Changeset 3739702
- Timestamp:
- 10/11/2026 03:38:26 PM (13 days ago)
- Location:
- bookingfacil-reservas
- Files:
-
- 16 edited
- 1 copied
-
tags/1.3.23 (copied) (copied from bookingfacil-reservas/trunk )
-
tags/1.3.23/assets/css/admin/admin-reservations.css (modified) (3 diffs)
-
tags/1.3.23/assets/js/admin/admin-accommodations.js (modified) (1 diff)
-
tags/1.3.23/assets/js/admin/admin-reservations.js (modified) (17 diffs)
-
tags/1.3.23/assets/js/frontend/frontend-search.js (modified) (1 diff)
-
tags/1.3.23/booking-facil.php (modified) (2 diffs)
-
tags/1.3.23/includes/admin-pages/reservations.php (modified) (1 diff)
-
tags/1.3.23/includes/admin-pages/units.php (modified) (1 diff)
-
tags/1.3.23/readme.txt (modified) (3 diffs)
-
trunk/assets/css/admin/admin-reservations.css (modified) (3 diffs)
-
trunk/assets/js/admin/admin-accommodations.js (modified) (1 diff)
-
trunk/assets/js/admin/admin-reservations.js (modified) (17 diffs)
-
trunk/assets/js/frontend/frontend-search.js (modified) (1 diff)
-
trunk/booking-facil.php (modified) (2 diffs)
-
trunk/includes/admin-pages/reservations.php (modified) (1 diff)
-
trunk/includes/admin-pages/units.php (modified) (1 diff)
-
trunk/readme.txt (modified) (3 diffs)
Legend:
- Unmodified
- Added
- Removed
-
bookingfacil-reservas/tags/1.3.23/assets/css/admin/admin-reservations.css
r3719967 r3739702 403 403 flex-direction: column; 404 404 } 405 405 406 .wp-booking-manual-col-left, 406 407 .wp-booking-manual-col-right { … … 516 517 } 517 518 519 /* Aviso de carga atrasada (check-in anterior a hoy) */ 520 .wp-booking-quote-past-notice { 521 padding: 8px 12px; 522 margin-bottom: 8px; 523 background: rgba(245, 158, 11, 0.1); 524 border: 1px solid #f59e0b; 525 border-radius: 6px; 526 color: #fcd34d; 527 font-size: 12px; 528 } 529 518 530 .wp-booking-quote-card-success { 519 531 background: rgba(16, 185, 129, 0.08); … … 990 1002 grid-template-columns: 1fr; 991 1003 } 1004 992 1005 .wp-booking-occ-doc-row { 993 1006 flex-direction: column; 994 1007 } 1008 995 1009 .wp-booking-occ-doctype-select { 996 1010 width: 100% !important; 997 1011 } 998 1012 } 1013 1014 /* Selector de unidades en tarjetas (Nueva Reserva en Mostrador) */ 1015 .wp-booking-unit-cards { display: grid; gap: 8px; margin-top: 6px; } 1016 .wp-booking-unit-card { 1017 padding: 10px 12px; border: 1px solid #334155; border-radius: 8px; background: rgba(15, 23, 42, 0.5); 1018 cursor: pointer; display: flex; flex-direction: column; gap: 4px; transition: border-color .15s, background .15s; 1019 } 1020 .wp-booking-unit-card:hover, .wp-booking-unit-card:focus { border-color: #64748b; outline: none; } 1021 .wp-booking-unit-card.is-selected { border-color: #38bdf8; background: rgba(56, 189, 248, 0.08); box-shadow: 0 0 0 1px #38bdf8; } 1022 .wp-booking-unit-card.is-unavailable { opacity: .85; } 1023 .wp-booking-unit-card-head { display: flex; justify-content: space-between; align-items: baseline; gap: 8px; } 1024 .wp-booking-unit-card-head small { color: #94a3b8; } 1025 .wp-booking-unit-card-badge { font-size: 12px; font-weight: 600; } 1026 .wp-booking-unit-card-badge.ok { color: #34d399; } 1027 .wp-booking-unit-card-badge.no { color: #f87171; } 1028 .wp-booking-unit-card-price { font-size: 13px; font-weight: 600; } 1029 .wp-booking-unit-card-reason { font-size: 12px; color: #cbd5e1; line-height: 1.4; } 1030 .wp-booking-unit-card-hint { font-size: 11px; color: #94a3b8; } 1031 .wp-booking-unit-card-hint.warn { color: #fcd34d; } 1032 1033 /* Aviso de reserva forzada (sin disponibilidad validada) */ 1034 .wp-booking-quote-override-warning { 1035 padding: 10px 12px; margin-bottom: 8px; background: rgba(245, 158, 11, 0.1); border: 1px solid #f59e0b; 1036 border-radius: 6px; color: #fcd34d; font-size: 12px; line-height: 1.5; 1037 } 1038 .wp-booking-quote-override-warning p { margin: 6px 0 0; } -
bookingfacil-reservas/tags/1.3.23/assets/js/admin/admin-accommodations.js
r3692007 r3739702 309 309 const children = parseInt(document.getElementById('wp-booking-prop-children')?.value, 10) || 0; 310 310 const totalGuests = adults + children; 311 const totalEl = document.getElementById('wp-booking-prop-capacity-total'); 312 if (totalEl) { 313 totalEl.textContent = `Capacidad total: ${totalGuests} persona${totalGuests !== 1 ? 's' : ''}` + 314 (adults > 0 ? ` (hasta ${adults} adulto${adults !== 1 ? 's' : ''}${children > 0 ? `; ${children} plaza${children !== 1 ? 's' : ''} solo para niños` : ''})` : ''); 315 } 311 316 const highlight = document.getElementById('wp-booking-prop-highlight')?.value?.trim(); 312 317 -
bookingfacil-reservas/tags/1.3.23/assets/js/admin/admin-reservations.js
r3737447 r3739702 86 86 let cachedGuestsList = []; 87 87 let currentManualQuote = null; 88 let currentManualForced = false; 89 let manualUnitStatus = {}; 90 const manualUnitCards = document.getElementById('wp-booking-manual-unit-cards'); 88 91 let currentManualGrandTotal = 0; 89 92 let manualQuoteDebounce = null; … … 2381 2384 }).then(data => { 2382 2385 const searchResults = data && data.success && Array.isArray(data.results) ? data.results : []; 2386 const unavailableList = data && Array.isArray(data.unavailable_units) ? data.unavailable_units : []; 2383 2387 const currentSelectedUnitId = manualUnitSelect ? manualUnitSelect.value : ''; 2384 2388 let hasAvailableSelected = false; 2389 2390 // Estado por unidad: disponible (con precio) o no disponible (con motivo) 2391 manualUnitStatus = {}; 2392 allUnits.forEach(u => { 2393 const match = searchResults.find(r => Number(r.id) === Number(u.id)); 2394 const why = unavailableList.find(x => Number(x.id) === Number(u.id)); 2395 if (match && match.available && match.total_price !== null) { 2396 manualUnitStatus[u.id] = { available: true, price: Number(match.total_price) }; 2397 } else { 2398 manualUnitStatus[u.id] = { 2399 available: false, 2400 reason: why || { code: 'unknown', overridable: false, message: 'No disponible para las fechas seleccionadas.' } 2401 }; 2402 } 2403 }); 2385 2404 2386 2405 if (manualUnitSelect) { 2387 2406 manualUnitSelect.innerHTML = ''; 2388 2407 allUnits.forEach(u => { 2389 const match = searchResults.find(r => Number(r.id) === Number(u.id));2408 const st = manualUnitStatus[u.id]; 2390 2409 const opt = document.createElement('option'); 2391 2410 opt.value = u.id; 2392 const uCapacity = u.capacity ? u.capacity.adults : u.adultsCapacity || 2; 2393 2394 if (match && match.available && match.total_price !== null) { 2395 opt.textContent = `🟢 ${u.name} — Disponible ($${Number(match.total_price).toLocaleString('es-AR')} total)`; 2396 if (Number(u.id) === Number(currentSelectedUnitId)) hasAvailableSelected = true; 2397 } else { 2398 opt.textContent = `🔴 ${u.name} — Sin disponibilidad (Capacidad: ${uCapacity} pers.)`; 2399 } 2411 opt.textContent = st.available ? `${u.name} — Disponible` : `${u.name} — ${st.reason.message}`; 2400 2412 manualUnitSelect.appendChild(opt); 2413 if (st.available && Number(u.id) === Number(currentSelectedUnitId)) hasAvailableSelected = true; 2401 2414 }); 2402 2415 2403 // Si la unidad actual no est aba disponible o no había seleccionada, elegir la primera disponible2416 // Si la unidad actual no está disponible, elegir la primera disponible (el operador puede elegir otra a mano) 2404 2417 if (!hasAvailableSelected) { 2405 const firstAvailable = searchResults.find(r => r.available && r.total_price !== null);2418 const firstAvailable = allUnits.find(u => manualUnitStatus[u.id].available); 2406 2419 if (firstAvailable) { 2407 2420 manualUnitSelect.value = firstAvailable.id; 2421 } else if (currentSelectedUnitId && allUnits.some(u => Number(u.id) === Number(currentSelectedUnitId))) { 2422 manualUnitSelect.value = currentSelectedUnitId; 2408 2423 } else if (allUnits.length > 0) { 2409 2424 manualUnitSelect.value = allUnits[0].id; … … 2413 2428 } 2414 2429 2430 renderManualUnitCards(); 2415 2431 updateManualUnitServicesAndPhysical(manualUnitSelect.value); 2416 2432 } … … 2419 2435 }).catch(() => { 2420 2436 calculateManualQuote(); 2437 }); 2438 } 2439 2440 function renderManualUnitCards() { 2441 if (!manualUnitCards || !manualUnitSelect) return; 2442 const selectedId = manualUnitSelect.value; 2443 manualUnitCards.innerHTML = ''; 2444 allUnits.forEach(u => { 2445 const st = manualUnitStatus[u.id]; 2446 if (!st) return; 2447 const isSelected = Number(u.id) === Number(selectedId); 2448 const cap = u.capacity ? u.capacity.adults : u.adultsCapacity || 2; 2449 const capKids = u.capacity ? u.capacity.children : u.childrenCapacity || 0; 2450 const card = document.createElement('div'); 2451 card.className = 'wp-booking-unit-card ' + (st.available ? 'is-available' : 'is-unavailable') + (isSelected ? ' is-selected' : ''); 2452 card.setAttribute('role', 'radio'); 2453 card.setAttribute('aria-checked', isSelected ? 'true' : 'false'); 2454 card.tabIndex = 0; 2455 card.dataset.unitId = u.id; 2456 let statusHtml; 2457 if (st.available) { 2458 statusHtml = `<span class="wp-booking-unit-card-badge ok">🟢 Disponible</span><span class="wp-booking-unit-card-price">$${st.price.toLocaleString('es-AR')} total</span>`; 2459 } else { 2460 const tag = st.reason.overridable ? 'Reservable bajo responsabilidad' : 'No reservable'; 2461 statusHtml = `<span class="wp-booking-unit-card-badge no">🔴 Sin disponibilidad</span>` + 2462 `<span class="wp-booking-unit-card-reason">${escapeHtml(st.reason.message)}</span>` + 2463 `<span class="wp-booking-unit-card-hint ${st.reason.overridable ? 'warn' : ''}">${tag}</span>`; 2464 } 2465 card.innerHTML = `<div class="wp-booking-unit-card-head"><strong>${escapeHtml(u.name)}</strong><small>👥 ${cap} ad.${capKids ? ' + ' + capKids + ' niños' : ''}</small></div>${statusHtml}`; 2466 const choose = () => { 2467 manualUnitSelect.value = u.id; 2468 manualUnitSelect.dispatchEvent(new Event('change')); 2469 renderManualUnitCards(); 2470 }; 2471 card.addEventListener('click', choose); 2472 card.addEventListener('keydown', e => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); choose(); } }); 2473 manualUnitCards.appendChild(card); 2421 2474 }); 2422 2475 } … … 2442 2495 } 2443 2496 2444 const todayStr = getLocalDateString(new Date());2445 if (checkin < todayStr) {2446 manualQuotePreview.innerHTML = '<div class="wp-booking-quote-error-msg">⚠️ La fecha de check-in no puede ser anterior a hoy.</div>';2447 if (manualSaveBtn) manualSaveBtn.disabled = true;2448 return;2449 }2450 2451 2497 if (checkin >= checkout) { 2452 2498 manualQuotePreview.innerHTML = '<div class="wp-booking-quote-error-msg">⚠️ La fecha de check-out debe ser posterior al check-in.</div>'; … … 2469 2515 children, 2470 2516 quantity, 2471 extra_services: extraServices 2517 extra_services: extraServices, 2518 admin_preview: true 2472 2519 }).then(data => { 2473 2520 if (data.success) { … … 2498 2545 const finalGrandTotal = baseAccommodation + extraServicesTotal; 2499 2546 2500 if (data.available) { 2547 // Aviso (no bloqueante) para carga de reservas atrasadas 2548 const checkinVal = manualCheckinInput ? manualCheckinInput.value : ''; 2549 const isPastCheckin = checkinVal && checkinVal < getLocalDateString(new Date()); 2550 const pastNoticeHtml = isPastCheckin 2551 ? `<div class="wp-booking-quote-past-notice">🕓 Estás cargando una reserva con ingreso anterior a hoy (${escapeHtml(checkinVal.split('-').reverse().join('/'))}). Se registrará como carga atrasada.</div>` 2552 : ''; 2553 2554 const unav = data.unavailability; 2555 const forceable = !data.available && !!unav && unav.overridable === true && data.has_full_coverage !== false; 2556 currentManualForced = forceable; 2557 2558 const overrideWarningHtml = forceable ? ` 2559 <div class="wp-booking-quote-override-warning"> 2560 <strong>⚠️ Reserva fuera de la disponibilidad validada por el sistema</strong> 2561 <p>${escapeHtml(unav.message)}</p> 2562 <p>Podés registrar la reserva de todos modos. Es responsabilidad del operador verificar la disponibilidad real del establecimiento y asignar manualmente a los pasajeros en la reserva (unidad física, fechas o alojamiento alternativo), evitando superposiciones con otras estadías.</p> 2563 </div>` : ''; 2564 2565 if (data.available || forceable) { 2501 2566 let extrasHtml = ''; 2502 2567 if (extraItems.length > 0) { … … 2508 2573 } 2509 2574 2510 manualQuotePreview.innerHTML = `2575 manualQuotePreview.innerHTML = pastNoticeHtml + overrideWarningHtml + ` 2511 2576 <div class="wp-booking-quote-card-success"> 2512 2577 <div class="wp-booking-quote-header"> 2513 <strong> ✅ ¡Estadía Disponible!(${nightsCount} noche${nightsCount !== 1 ? 's' : ''})</strong>2514 <span class="wp-booking-badge-success"> Disponible</span>2578 <strong>${forceable ? '⚠️ Cotización de la estadía' : '✅ ¡Estadía Disponible!'} (${nightsCount} noche${nightsCount !== 1 ? 's' : ''})</strong> 2579 <span class="wp-booking-badge-success">${forceable ? 'Sin disponibilidad' : 'Disponible'}</span> 2515 2580 </div> 2516 2581 <div class="wp-booking-quote-body"> … … 2530 2595 } else { 2531 2596 currentManualGrandTotal = 0; 2532 manualQuotePreview.innerHTML = ` 2597 const noRates = data.has_full_coverage === false; 2598 const blockMsg = unav && unav.message ? `<br><small>${escapeHtml(unav.message)}</small>` : ''; 2599 manualQuotePreview.innerHTML = pastNoticeHtml + (!noRates && !forceable && unav ? ` 2600 <div class="wp-booking-quote-card-error"> 2601 <strong>⚠️ No se puede registrar esta reserva</strong>${blockMsg} 2602 </div> 2603 ` : noRates ? ` 2604 <div class="wp-booking-quote-card-error"> 2605 <strong>⚠️ No hay tarifas cargadas</strong> para todas las noches seleccionadas.${blockMsg}<br> 2606 <small>Cargá una tarifa que cubra esas fechas en "Tarifas" para poder registrar la reserva${isPastCheckin ? ' (también aplica a fechas pasadas)' : ''}.</small> 2607 </div> 2608 ` : ` 2533 2609 <div class="wp-booking-quote-card-error"> 2534 2610 <strong>⚠️ Sin disponibilidad suficiente</strong> para las fechas solicitadas.<br> 2535 <small>Capacidad disponible en este período: ${data. min_availability !== undefined ? data.min_availability: 0} unidad(es).</small>2611 <small>Capacidad disponible en este período: ${data.available_count !== undefined ? data.available_count : 0} unidad(es).</small> 2536 2612 </div> 2537 ` ;2613 `); 2538 2614 if (manualSaveBtn) manualSaveBtn.disabled = true; 2539 2615 updateManualFinancialSummary(); … … 2670 2746 altFormat: 'd/m/Y', 2671 2747 allowInput: false, 2672 minDate: 'today',2748 // Sin fecha mínima: los propietarios necesitan cargar reservas atrasadas (fechas anteriores a hoy) 2673 2749 onChange: function (selectedDates) { 2674 2750 if (selectedDates.length > 0 && manualCheckoutFp) { … … 2733 2809 2734 2810 if (manualCheckinFp) { 2735 manualCheckinFp.set('minDate', 'today');2736 2811 manualCheckinFp.setDate(today, false); 2737 2812 } else if (manualCheckinInput) { 2738 manualCheckinInput. min = todayStr;2813 manualCheckinInput.removeAttribute('min'); 2739 2814 manualCheckinInput.value = todayStr; 2740 2815 } … … 2787 2862 } 2788 2863 2864 // Si la estadía ya finalizó (carga atrasada), no tiene sentido enviar la confirmación al huésped 2865 function isManualStayFinished() { 2866 const out = manualCheckoutInput ? manualCheckoutInput.value : ''; 2867 return !!out && out <= getLocalDateString(new Date()); 2868 } 2869 2789 2870 if (manualGuestEmailInput && manualSendGuestEmailCheck) { 2790 2871 manualGuestEmailInput.addEventListener('input', function () { 2791 if (this.value.trim().length > 0 ) {2872 if (this.value.trim().length > 0 && !isManualStayFinished()) { 2792 2873 manualSendGuestEmailCheck.checked = true; 2793 2874 } 2794 2875 }); 2795 2876 } 2877 2878 [manualCheckinInput, manualCheckoutInput].forEach(input => { 2879 if (input && manualSendGuestEmailCheck) { 2880 input.addEventListener('change', function () { 2881 if (isManualStayFinished()) manualSendGuestEmailCheck.checked = false; 2882 }); 2883 } 2884 }); 2796 2885 2797 2886 function showManualSearchResults() { … … 2893 2982 if (manualUnitSelect) { 2894 2983 manualUnitSelect.addEventListener('change', function () { 2984 renderManualUnitCards(); 2895 2985 updateManualUnitServicesAndPhysical(this.value); 2896 2986 debounceCalculateManualQuote(); … … 2901 2991 manualCheckinInput.addEventListener('change', function () { 2902 2992 const todayStr = getLocalDateString(new Date()); 2903 this.min = todayStr;2904 if (this.value && this.value < todayStr) {2905 this.value = todayStr;2906 }2907 2993 const inVal = this.value || todayStr; 2908 2994 const inDate = new Date(inVal + 'T00:00:00'); … … 2988 3074 } 2989 3075 3076 // Carga atrasada: se permite registrar reservas con ingreso anterior a hoy, previa confirmación 2990 3077 const todayStr = getLocalDateString(new Date()); 2991 3078 if (checkin < todayStr) { 2992 if (feedbackManual) { 2993 feedbackManual.className = 'wp-booking-admin-feedback error'; 2994 feedbackManual.style.display = 'block'; 2995 feedbackManual.textContent = 'La fecha de check-in no puede ser anterior al día de hoy.'; 2996 } 2997 return; 3079 const fmt = s => s.split('-').reverse().join('/'); 3080 const stayFinished = checkout <= todayStr; 3081 const pastMsg = `Estás registrando una reserva con fecha de ingreso anterior a hoy:\n\n` + 3082 `Check-in: ${fmt(checkin)} → Check-out: ${fmt(checkout)}\n` + 3083 (stayFinished ? `\nLa estadía ya finalizó.` : `\nLa estadía está en curso.`) + 3084 `\n\n¿Confirmás la carga de esta reserva atrasada?`; 3085 if (!confirm(pastMsg)) return; 2998 3086 } 2999 3087 … … 3005 3093 } 3006 3094 return; 3095 } 3096 3097 if (currentManualForced) { 3098 const reasonTxt = currentManualQuote && currentManualQuote.unavailability ? currentManualQuote.unavailability.message : 'La unidad no tiene disponibilidad para el período seleccionado.'; 3099 const forceMsg = `ATENCIÓN: RESERVA SIN DISPONIBILIDAD VALIDADA\n\n` + 3100 `El sistema indica que esta unidad no está disponible para el período seleccionado:\n• ${reasonTxt}\n\n` + 3101 `Si continúa, la reserva se registrará de todos modos. Es responsabilidad del operador verificar la disponibilidad real del establecimiento y asignar a los pasajeros en la reserva (unidad física, fechas o alojamiento alternativo) para evitar superposiciones con otras estadías.\n\n` + 3102 `¿Desea registrar la reserva de todos modos?`; 3103 if (!confirm(forceMsg)) return; 3007 3104 } 3008 3105 … … 3050 3147 payment_method: 'destino', 3051 3148 is_admin_manual: true, 3149 allow_unavailable: currentManualForced === true, 3052 3150 send_guest_email: (manualSendGuestEmailCheck && guestEmail.length > 0) ? manualSendGuestEmailCheck.checked : false, 3053 3151 status: paymentMode === 'none' ? 'pending_payment' : 'confirmed', -
bookingfacil-reservas/tags/1.3.23/assets/js/frontend/frontend-search.js
r3703315 r3739702 471 471 </div> 472 472 <div class="wp-booking-range-unit-meta"> 473 <span class="wp-booking-range-meta-pill">👥 Capacidad: ${parseInt(unit.capacity.adults, 10)} adultos</span>473 <span class="wp-booking-range-meta-pill">👥 Hasta ${(parseInt(unit.capacity.adults, 10) || 0) + (parseInt(unit.capacity.children, 10) || 0)} personas</span> 474 474 ${minNightsBadge} 475 475 </div> -
bookingfacil-reservas/tags/1.3.23/booking-facil.php
r3738258 r3739702 3 3 * Plugin Name: BookingFacil Reservations for Accommodations 4 4 * Description: Accept online reservations for accommodations and campsites. Manage rates, parcel occupancy, payments, and confirmations from WordPress. 5 * Version: 1.3.2 25 * Version: 1.3.23 6 6 * Requires at least: 5.8 7 7 * Requires PHP: 7.4 … … 20 20 21 21 // Definir constantes del plugin 22 define( 'BOOKING_FACIL_VERSION', '1.3.2 2' );22 define( 'BOOKING_FACIL_VERSION', '1.3.23' ); 23 23 define( 'BOOKING_FACIL_PATH', plugin_dir_path( __FILE__ ) ); 24 24 define( 'BOOKING_FACIL_URL', plugin_dir_url( __FILE__ ) ); -
bookingfacil-reservas/tags/1.3.23/includes/admin-pages/reservations.php
r3721666 r3739702 776 776 <div class="wp-booking-form-group wbf-style-b0473a88"> 777 777 <label for="wp-booking-manual-unit-id" class="wbf-style-16fa3e7d"><?php esc_html_e( 'Tipo de Unidad Habitacional *', 'bookingfacil-reservas' ); ?></label> 778 <select id="wp-booking-manual-unit-id" required class="wbf-style-48c62ea1" >778 <select id="wp-booking-manual-unit-id" required class="wbf-style-48c62ea1" style="display:none;" aria-hidden="true" tabindex="-1"> 779 779 <option value=""><?php esc_html_e( 'Seleccione una unidad...', 'bookingfacil-reservas' ); ?></option> 780 780 </select> 781 <div id="wp-booking-manual-unit-cards" class="wp-booking-unit-cards" role="radiogroup" aria-label="<?php esc_attr_e( 'Tipo de unidad habitacional', 'bookingfacil-reservas' ); ?>"> 782 <div class="wp-booking-quote-empty-msg"><?php esc_html_e( 'Seleccioná las fechas para ver la disponibilidad de cada unidad.', 'bookingfacil-reservas' ); ?></div> 783 </div> 781 784 </div> 782 785 -
bookingfacil-reservas/tags/1.3.23/includes/admin-pages/units.php
r3692007 r3739702 112 112 <div class="wp-booking-form-row"> 113 113 <div class="wp-booking-form-group"> 114 <label for="wp-booking-prop-adults"><?php esc_html_e( ' Capacidad Adultos *', 'bookingfacil-reservas' ); ?></label>114 <label for="wp-booking-prop-adults"><?php esc_html_e( 'Plazas para adultos *', 'bookingfacil-reservas' ); ?></label> 115 115 <input type="number" id="wp-booking-prop-adults" min="1" value="2" required /> 116 <small class="description"><?php esc_html_e( 'Los niños también pueden ocupar estas plazas.', 'bookingfacil-reservas' ); ?></small> 116 117 </div> 117 118 <div class="wp-booking-form-group"> 118 <label for="wp-booking-prop-children"><?php esc_html_e( ' Capacidad Niños', 'bookingfacil-reservas' ); ?></label>119 <label for="wp-booking-prop-children"><?php esc_html_e( 'Plazas adicionales solo para niños', 'bookingfacil-reservas' ); ?></label> 119 120 <input type="number" id="wp-booking-prop-children" min="0" value="0" /> 120 </div> 121 </div> 121 <small class="description"><?php esc_html_e( 'Dejalo en 0 si la unidad no tiene camas o cunas extra para niños.', 'bookingfacil-reservas' ); ?></small> 122 </div> 123 </div> 124 <p id="wp-booking-prop-capacity-total" class="description" style="margin:-4px 0 12px;font-weight:600;"></p> 122 125 123 126 <!-- ESPECIFICACIONES CLAVE: DORMITORIOS Y BAÑOS --> -
bookingfacil-reservas/tags/1.3.23/readme.txt
r3738258 r3739702 5 5 Tested up to: 7.1.2 6 6 Requires PHP: 7.4 7 Stable tag: 1.3.2 27 Stable tag: 1.3.23 8 8 License: GPLv2 or later 9 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 88 88 == Changelog == 89 89 90 = 1.3.23 = 91 * Manual Booking Creation: allows lodging managers to register delayed or ongoing bookings with check-in dates prior to today, with an intuitive confirmation prompt. 92 * Automatic Email Suppression for Past Stays: automatically disables email delivery to guests if the manual stay has already finished. 93 * Interactive Unit Availability Cards: introduced interactive visual unit cards in manual reservations displaying live rates, available physical counts, and detailed blockage/unavailability reasons. 94 * Operator Availability Override: allows operators to proceed with booking registration under manual responsibility when units lack validated online inventory. 95 * Adult & Child Capacity Definitions: clarified adult bed capacities (which children can also occupy) vs. extra child-only beds/cribs with real-time total person counters in unit settings. 96 * Public Search Capacity Display: updated range recommendations and availability chips to state "Hasta X personas" including adult and child capacities. 97 90 98 = 1.3.22 = 91 99 * Frontdesk Daily Operations: added interactive confirmation alert prior to check-in registration to prevent accidental clicks. … … 252 260 == Upgrade Notice == 253 261 262 = 1.3.23 = 263 * Recommended update: enhances manual booking creation with delayed/past stay support, interactive unit availability cards, forced override under operator responsibility, and clarified adult/child capacity handling. 264 254 265 = 1.3.22 = 255 266 * Recommended update: enhances Frontdesk check-in workflow with interactive confirmation, automatic transfer to In-House Guests, reversible check-in, and clear visual distinction between vacant and occupied units in Housekeeping Express. -
bookingfacil-reservas/trunk/assets/css/admin/admin-reservations.css
r3719967 r3739702 403 403 flex-direction: column; 404 404 } 405 405 406 .wp-booking-manual-col-left, 406 407 .wp-booking-manual-col-right { … … 516 517 } 517 518 519 /* Aviso de carga atrasada (check-in anterior a hoy) */ 520 .wp-booking-quote-past-notice { 521 padding: 8px 12px; 522 margin-bottom: 8px; 523 background: rgba(245, 158, 11, 0.1); 524 border: 1px solid #f59e0b; 525 border-radius: 6px; 526 color: #fcd34d; 527 font-size: 12px; 528 } 529 518 530 .wp-booking-quote-card-success { 519 531 background: rgba(16, 185, 129, 0.08); … … 990 1002 grid-template-columns: 1fr; 991 1003 } 1004 992 1005 .wp-booking-occ-doc-row { 993 1006 flex-direction: column; 994 1007 } 1008 995 1009 .wp-booking-occ-doctype-select { 996 1010 width: 100% !important; 997 1011 } 998 1012 } 1013 1014 /* Selector de unidades en tarjetas (Nueva Reserva en Mostrador) */ 1015 .wp-booking-unit-cards { display: grid; gap: 8px; margin-top: 6px; } 1016 .wp-booking-unit-card { 1017 padding: 10px 12px; border: 1px solid #334155; border-radius: 8px; background: rgba(15, 23, 42, 0.5); 1018 cursor: pointer; display: flex; flex-direction: column; gap: 4px; transition: border-color .15s, background .15s; 1019 } 1020 .wp-booking-unit-card:hover, .wp-booking-unit-card:focus { border-color: #64748b; outline: none; } 1021 .wp-booking-unit-card.is-selected { border-color: #38bdf8; background: rgba(56, 189, 248, 0.08); box-shadow: 0 0 0 1px #38bdf8; } 1022 .wp-booking-unit-card.is-unavailable { opacity: .85; } 1023 .wp-booking-unit-card-head { display: flex; justify-content: space-between; align-items: baseline; gap: 8px; } 1024 .wp-booking-unit-card-head small { color: #94a3b8; } 1025 .wp-booking-unit-card-badge { font-size: 12px; font-weight: 600; } 1026 .wp-booking-unit-card-badge.ok { color: #34d399; } 1027 .wp-booking-unit-card-badge.no { color: #f87171; } 1028 .wp-booking-unit-card-price { font-size: 13px; font-weight: 600; } 1029 .wp-booking-unit-card-reason { font-size: 12px; color: #cbd5e1; line-height: 1.4; } 1030 .wp-booking-unit-card-hint { font-size: 11px; color: #94a3b8; } 1031 .wp-booking-unit-card-hint.warn { color: #fcd34d; } 1032 1033 /* Aviso de reserva forzada (sin disponibilidad validada) */ 1034 .wp-booking-quote-override-warning { 1035 padding: 10px 12px; margin-bottom: 8px; background: rgba(245, 158, 11, 0.1); border: 1px solid #f59e0b; 1036 border-radius: 6px; color: #fcd34d; font-size: 12px; line-height: 1.5; 1037 } 1038 .wp-booking-quote-override-warning p { margin: 6px 0 0; } -
bookingfacil-reservas/trunk/assets/js/admin/admin-accommodations.js
r3692007 r3739702 309 309 const children = parseInt(document.getElementById('wp-booking-prop-children')?.value, 10) || 0; 310 310 const totalGuests = adults + children; 311 const totalEl = document.getElementById('wp-booking-prop-capacity-total'); 312 if (totalEl) { 313 totalEl.textContent = `Capacidad total: ${totalGuests} persona${totalGuests !== 1 ? 's' : ''}` + 314 (adults > 0 ? ` (hasta ${adults} adulto${adults !== 1 ? 's' : ''}${children > 0 ? `; ${children} plaza${children !== 1 ? 's' : ''} solo para niños` : ''})` : ''); 315 } 311 316 const highlight = document.getElementById('wp-booking-prop-highlight')?.value?.trim(); 312 317 -
bookingfacil-reservas/trunk/assets/js/admin/admin-reservations.js
r3737447 r3739702 86 86 let cachedGuestsList = []; 87 87 let currentManualQuote = null; 88 let currentManualForced = false; 89 let manualUnitStatus = {}; 90 const manualUnitCards = document.getElementById('wp-booking-manual-unit-cards'); 88 91 let currentManualGrandTotal = 0; 89 92 let manualQuoteDebounce = null; … … 2381 2384 }).then(data => { 2382 2385 const searchResults = data && data.success && Array.isArray(data.results) ? data.results : []; 2386 const unavailableList = data && Array.isArray(data.unavailable_units) ? data.unavailable_units : []; 2383 2387 const currentSelectedUnitId = manualUnitSelect ? manualUnitSelect.value : ''; 2384 2388 let hasAvailableSelected = false; 2389 2390 // Estado por unidad: disponible (con precio) o no disponible (con motivo) 2391 manualUnitStatus = {}; 2392 allUnits.forEach(u => { 2393 const match = searchResults.find(r => Number(r.id) === Number(u.id)); 2394 const why = unavailableList.find(x => Number(x.id) === Number(u.id)); 2395 if (match && match.available && match.total_price !== null) { 2396 manualUnitStatus[u.id] = { available: true, price: Number(match.total_price) }; 2397 } else { 2398 manualUnitStatus[u.id] = { 2399 available: false, 2400 reason: why || { code: 'unknown', overridable: false, message: 'No disponible para las fechas seleccionadas.' } 2401 }; 2402 } 2403 }); 2385 2404 2386 2405 if (manualUnitSelect) { 2387 2406 manualUnitSelect.innerHTML = ''; 2388 2407 allUnits.forEach(u => { 2389 const match = searchResults.find(r => Number(r.id) === Number(u.id));2408 const st = manualUnitStatus[u.id]; 2390 2409 const opt = document.createElement('option'); 2391 2410 opt.value = u.id; 2392 const uCapacity = u.capacity ? u.capacity.adults : u.adultsCapacity || 2; 2393 2394 if (match && match.available && match.total_price !== null) { 2395 opt.textContent = `🟢 ${u.name} — Disponible ($${Number(match.total_price).toLocaleString('es-AR')} total)`; 2396 if (Number(u.id) === Number(currentSelectedUnitId)) hasAvailableSelected = true; 2397 } else { 2398 opt.textContent = `🔴 ${u.name} — Sin disponibilidad (Capacidad: ${uCapacity} pers.)`; 2399 } 2411 opt.textContent = st.available ? `${u.name} — Disponible` : `${u.name} — ${st.reason.message}`; 2400 2412 manualUnitSelect.appendChild(opt); 2413 if (st.available && Number(u.id) === Number(currentSelectedUnitId)) hasAvailableSelected = true; 2401 2414 }); 2402 2415 2403 // Si la unidad actual no est aba disponible o no había seleccionada, elegir la primera disponible2416 // Si la unidad actual no está disponible, elegir la primera disponible (el operador puede elegir otra a mano) 2404 2417 if (!hasAvailableSelected) { 2405 const firstAvailable = searchResults.find(r => r.available && r.total_price !== null);2418 const firstAvailable = allUnits.find(u => manualUnitStatus[u.id].available); 2406 2419 if (firstAvailable) { 2407 2420 manualUnitSelect.value = firstAvailable.id; 2421 } else if (currentSelectedUnitId && allUnits.some(u => Number(u.id) === Number(currentSelectedUnitId))) { 2422 manualUnitSelect.value = currentSelectedUnitId; 2408 2423 } else if (allUnits.length > 0) { 2409 2424 manualUnitSelect.value = allUnits[0].id; … … 2413 2428 } 2414 2429 2430 renderManualUnitCards(); 2415 2431 updateManualUnitServicesAndPhysical(manualUnitSelect.value); 2416 2432 } … … 2419 2435 }).catch(() => { 2420 2436 calculateManualQuote(); 2437 }); 2438 } 2439 2440 function renderManualUnitCards() { 2441 if (!manualUnitCards || !manualUnitSelect) return; 2442 const selectedId = manualUnitSelect.value; 2443 manualUnitCards.innerHTML = ''; 2444 allUnits.forEach(u => { 2445 const st = manualUnitStatus[u.id]; 2446 if (!st) return; 2447 const isSelected = Number(u.id) === Number(selectedId); 2448 const cap = u.capacity ? u.capacity.adults : u.adultsCapacity || 2; 2449 const capKids = u.capacity ? u.capacity.children : u.childrenCapacity || 0; 2450 const card = document.createElement('div'); 2451 card.className = 'wp-booking-unit-card ' + (st.available ? 'is-available' : 'is-unavailable') + (isSelected ? ' is-selected' : ''); 2452 card.setAttribute('role', 'radio'); 2453 card.setAttribute('aria-checked', isSelected ? 'true' : 'false'); 2454 card.tabIndex = 0; 2455 card.dataset.unitId = u.id; 2456 let statusHtml; 2457 if (st.available) { 2458 statusHtml = `<span class="wp-booking-unit-card-badge ok">🟢 Disponible</span><span class="wp-booking-unit-card-price">$${st.price.toLocaleString('es-AR')} total</span>`; 2459 } else { 2460 const tag = st.reason.overridable ? 'Reservable bajo responsabilidad' : 'No reservable'; 2461 statusHtml = `<span class="wp-booking-unit-card-badge no">🔴 Sin disponibilidad</span>` + 2462 `<span class="wp-booking-unit-card-reason">${escapeHtml(st.reason.message)}</span>` + 2463 `<span class="wp-booking-unit-card-hint ${st.reason.overridable ? 'warn' : ''}">${tag}</span>`; 2464 } 2465 card.innerHTML = `<div class="wp-booking-unit-card-head"><strong>${escapeHtml(u.name)}</strong><small>👥 ${cap} ad.${capKids ? ' + ' + capKids + ' niños' : ''}</small></div>${statusHtml}`; 2466 const choose = () => { 2467 manualUnitSelect.value = u.id; 2468 manualUnitSelect.dispatchEvent(new Event('change')); 2469 renderManualUnitCards(); 2470 }; 2471 card.addEventListener('click', choose); 2472 card.addEventListener('keydown', e => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); choose(); } }); 2473 manualUnitCards.appendChild(card); 2421 2474 }); 2422 2475 } … … 2442 2495 } 2443 2496 2444 const todayStr = getLocalDateString(new Date());2445 if (checkin < todayStr) {2446 manualQuotePreview.innerHTML = '<div class="wp-booking-quote-error-msg">⚠️ La fecha de check-in no puede ser anterior a hoy.</div>';2447 if (manualSaveBtn) manualSaveBtn.disabled = true;2448 return;2449 }2450 2451 2497 if (checkin >= checkout) { 2452 2498 manualQuotePreview.innerHTML = '<div class="wp-booking-quote-error-msg">⚠️ La fecha de check-out debe ser posterior al check-in.</div>'; … … 2469 2515 children, 2470 2516 quantity, 2471 extra_services: extraServices 2517 extra_services: extraServices, 2518 admin_preview: true 2472 2519 }).then(data => { 2473 2520 if (data.success) { … … 2498 2545 const finalGrandTotal = baseAccommodation + extraServicesTotal; 2499 2546 2500 if (data.available) { 2547 // Aviso (no bloqueante) para carga de reservas atrasadas 2548 const checkinVal = manualCheckinInput ? manualCheckinInput.value : ''; 2549 const isPastCheckin = checkinVal && checkinVal < getLocalDateString(new Date()); 2550 const pastNoticeHtml = isPastCheckin 2551 ? `<div class="wp-booking-quote-past-notice">🕓 Estás cargando una reserva con ingreso anterior a hoy (${escapeHtml(checkinVal.split('-').reverse().join('/'))}). Se registrará como carga atrasada.</div>` 2552 : ''; 2553 2554 const unav = data.unavailability; 2555 const forceable = !data.available && !!unav && unav.overridable === true && data.has_full_coverage !== false; 2556 currentManualForced = forceable; 2557 2558 const overrideWarningHtml = forceable ? ` 2559 <div class="wp-booking-quote-override-warning"> 2560 <strong>⚠️ Reserva fuera de la disponibilidad validada por el sistema</strong> 2561 <p>${escapeHtml(unav.message)}</p> 2562 <p>Podés registrar la reserva de todos modos. Es responsabilidad del operador verificar la disponibilidad real del establecimiento y asignar manualmente a los pasajeros en la reserva (unidad física, fechas o alojamiento alternativo), evitando superposiciones con otras estadías.</p> 2563 </div>` : ''; 2564 2565 if (data.available || forceable) { 2501 2566 let extrasHtml = ''; 2502 2567 if (extraItems.length > 0) { … … 2508 2573 } 2509 2574 2510 manualQuotePreview.innerHTML = `2575 manualQuotePreview.innerHTML = pastNoticeHtml + overrideWarningHtml + ` 2511 2576 <div class="wp-booking-quote-card-success"> 2512 2577 <div class="wp-booking-quote-header"> 2513 <strong> ✅ ¡Estadía Disponible!(${nightsCount} noche${nightsCount !== 1 ? 's' : ''})</strong>2514 <span class="wp-booking-badge-success"> Disponible</span>2578 <strong>${forceable ? '⚠️ Cotización de la estadía' : '✅ ¡Estadía Disponible!'} (${nightsCount} noche${nightsCount !== 1 ? 's' : ''})</strong> 2579 <span class="wp-booking-badge-success">${forceable ? 'Sin disponibilidad' : 'Disponible'}</span> 2515 2580 </div> 2516 2581 <div class="wp-booking-quote-body"> … … 2530 2595 } else { 2531 2596 currentManualGrandTotal = 0; 2532 manualQuotePreview.innerHTML = ` 2597 const noRates = data.has_full_coverage === false; 2598 const blockMsg = unav && unav.message ? `<br><small>${escapeHtml(unav.message)}</small>` : ''; 2599 manualQuotePreview.innerHTML = pastNoticeHtml + (!noRates && !forceable && unav ? ` 2600 <div class="wp-booking-quote-card-error"> 2601 <strong>⚠️ No se puede registrar esta reserva</strong>${blockMsg} 2602 </div> 2603 ` : noRates ? ` 2604 <div class="wp-booking-quote-card-error"> 2605 <strong>⚠️ No hay tarifas cargadas</strong> para todas las noches seleccionadas.${blockMsg}<br> 2606 <small>Cargá una tarifa que cubra esas fechas en "Tarifas" para poder registrar la reserva${isPastCheckin ? ' (también aplica a fechas pasadas)' : ''}.</small> 2607 </div> 2608 ` : ` 2533 2609 <div class="wp-booking-quote-card-error"> 2534 2610 <strong>⚠️ Sin disponibilidad suficiente</strong> para las fechas solicitadas.<br> 2535 <small>Capacidad disponible en este período: ${data. min_availability !== undefined ? data.min_availability: 0} unidad(es).</small>2611 <small>Capacidad disponible en este período: ${data.available_count !== undefined ? data.available_count : 0} unidad(es).</small> 2536 2612 </div> 2537 ` ;2613 `); 2538 2614 if (manualSaveBtn) manualSaveBtn.disabled = true; 2539 2615 updateManualFinancialSummary(); … … 2670 2746 altFormat: 'd/m/Y', 2671 2747 allowInput: false, 2672 minDate: 'today',2748 // Sin fecha mínima: los propietarios necesitan cargar reservas atrasadas (fechas anteriores a hoy) 2673 2749 onChange: function (selectedDates) { 2674 2750 if (selectedDates.length > 0 && manualCheckoutFp) { … … 2733 2809 2734 2810 if (manualCheckinFp) { 2735 manualCheckinFp.set('minDate', 'today');2736 2811 manualCheckinFp.setDate(today, false); 2737 2812 } else if (manualCheckinInput) { 2738 manualCheckinInput. min = todayStr;2813 manualCheckinInput.removeAttribute('min'); 2739 2814 manualCheckinInput.value = todayStr; 2740 2815 } … … 2787 2862 } 2788 2863 2864 // Si la estadía ya finalizó (carga atrasada), no tiene sentido enviar la confirmación al huésped 2865 function isManualStayFinished() { 2866 const out = manualCheckoutInput ? manualCheckoutInput.value : ''; 2867 return !!out && out <= getLocalDateString(new Date()); 2868 } 2869 2789 2870 if (manualGuestEmailInput && manualSendGuestEmailCheck) { 2790 2871 manualGuestEmailInput.addEventListener('input', function () { 2791 if (this.value.trim().length > 0 ) {2872 if (this.value.trim().length > 0 && !isManualStayFinished()) { 2792 2873 manualSendGuestEmailCheck.checked = true; 2793 2874 } 2794 2875 }); 2795 2876 } 2877 2878 [manualCheckinInput, manualCheckoutInput].forEach(input => { 2879 if (input && manualSendGuestEmailCheck) { 2880 input.addEventListener('change', function () { 2881 if (isManualStayFinished()) manualSendGuestEmailCheck.checked = false; 2882 }); 2883 } 2884 }); 2796 2885 2797 2886 function showManualSearchResults() { … … 2893 2982 if (manualUnitSelect) { 2894 2983 manualUnitSelect.addEventListener('change', function () { 2984 renderManualUnitCards(); 2895 2985 updateManualUnitServicesAndPhysical(this.value); 2896 2986 debounceCalculateManualQuote(); … … 2901 2991 manualCheckinInput.addEventListener('change', function () { 2902 2992 const todayStr = getLocalDateString(new Date()); 2903 this.min = todayStr;2904 if (this.value && this.value < todayStr) {2905 this.value = todayStr;2906 }2907 2993 const inVal = this.value || todayStr; 2908 2994 const inDate = new Date(inVal + 'T00:00:00'); … … 2988 3074 } 2989 3075 3076 // Carga atrasada: se permite registrar reservas con ingreso anterior a hoy, previa confirmación 2990 3077 const todayStr = getLocalDateString(new Date()); 2991 3078 if (checkin < todayStr) { 2992 if (feedbackManual) { 2993 feedbackManual.className = 'wp-booking-admin-feedback error'; 2994 feedbackManual.style.display = 'block'; 2995 feedbackManual.textContent = 'La fecha de check-in no puede ser anterior al día de hoy.'; 2996 } 2997 return; 3079 const fmt = s => s.split('-').reverse().join('/'); 3080 const stayFinished = checkout <= todayStr; 3081 const pastMsg = `Estás registrando una reserva con fecha de ingreso anterior a hoy:\n\n` + 3082 `Check-in: ${fmt(checkin)} → Check-out: ${fmt(checkout)}\n` + 3083 (stayFinished ? `\nLa estadía ya finalizó.` : `\nLa estadía está en curso.`) + 3084 `\n\n¿Confirmás la carga de esta reserva atrasada?`; 3085 if (!confirm(pastMsg)) return; 2998 3086 } 2999 3087 … … 3005 3093 } 3006 3094 return; 3095 } 3096 3097 if (currentManualForced) { 3098 const reasonTxt = currentManualQuote && currentManualQuote.unavailability ? currentManualQuote.unavailability.message : 'La unidad no tiene disponibilidad para el período seleccionado.'; 3099 const forceMsg = `ATENCIÓN: RESERVA SIN DISPONIBILIDAD VALIDADA\n\n` + 3100 `El sistema indica que esta unidad no está disponible para el período seleccionado:\n• ${reasonTxt}\n\n` + 3101 `Si continúa, la reserva se registrará de todos modos. Es responsabilidad del operador verificar la disponibilidad real del establecimiento y asignar a los pasajeros en la reserva (unidad física, fechas o alojamiento alternativo) para evitar superposiciones con otras estadías.\n\n` + 3102 `¿Desea registrar la reserva de todos modos?`; 3103 if (!confirm(forceMsg)) return; 3007 3104 } 3008 3105 … … 3050 3147 payment_method: 'destino', 3051 3148 is_admin_manual: true, 3149 allow_unavailable: currentManualForced === true, 3052 3150 send_guest_email: (manualSendGuestEmailCheck && guestEmail.length > 0) ? manualSendGuestEmailCheck.checked : false, 3053 3151 status: paymentMode === 'none' ? 'pending_payment' : 'confirmed', -
bookingfacil-reservas/trunk/assets/js/frontend/frontend-search.js
r3703315 r3739702 471 471 </div> 472 472 <div class="wp-booking-range-unit-meta"> 473 <span class="wp-booking-range-meta-pill">👥 Capacidad: ${parseInt(unit.capacity.adults, 10)} adultos</span>473 <span class="wp-booking-range-meta-pill">👥 Hasta ${(parseInt(unit.capacity.adults, 10) || 0) + (parseInt(unit.capacity.children, 10) || 0)} personas</span> 474 474 ${minNightsBadge} 475 475 </div> -
bookingfacil-reservas/trunk/booking-facil.php
r3738258 r3739702 3 3 * Plugin Name: BookingFacil Reservations for Accommodations 4 4 * Description: Accept online reservations for accommodations and campsites. Manage rates, parcel occupancy, payments, and confirmations from WordPress. 5 * Version: 1.3.2 25 * Version: 1.3.23 6 6 * Requires at least: 5.8 7 7 * Requires PHP: 7.4 … … 20 20 21 21 // Definir constantes del plugin 22 define( 'BOOKING_FACIL_VERSION', '1.3.2 2' );22 define( 'BOOKING_FACIL_VERSION', '1.3.23' ); 23 23 define( 'BOOKING_FACIL_PATH', plugin_dir_path( __FILE__ ) ); 24 24 define( 'BOOKING_FACIL_URL', plugin_dir_url( __FILE__ ) ); -
bookingfacil-reservas/trunk/includes/admin-pages/reservations.php
r3721666 r3739702 776 776 <div class="wp-booking-form-group wbf-style-b0473a88"> 777 777 <label for="wp-booking-manual-unit-id" class="wbf-style-16fa3e7d"><?php esc_html_e( 'Tipo de Unidad Habitacional *', 'bookingfacil-reservas' ); ?></label> 778 <select id="wp-booking-manual-unit-id" required class="wbf-style-48c62ea1" >778 <select id="wp-booking-manual-unit-id" required class="wbf-style-48c62ea1" style="display:none;" aria-hidden="true" tabindex="-1"> 779 779 <option value=""><?php esc_html_e( 'Seleccione una unidad...', 'bookingfacil-reservas' ); ?></option> 780 780 </select> 781 <div id="wp-booking-manual-unit-cards" class="wp-booking-unit-cards" role="radiogroup" aria-label="<?php esc_attr_e( 'Tipo de unidad habitacional', 'bookingfacil-reservas' ); ?>"> 782 <div class="wp-booking-quote-empty-msg"><?php esc_html_e( 'Seleccioná las fechas para ver la disponibilidad de cada unidad.', 'bookingfacil-reservas' ); ?></div> 783 </div> 781 784 </div> 782 785 -
bookingfacil-reservas/trunk/includes/admin-pages/units.php
r3692007 r3739702 112 112 <div class="wp-booking-form-row"> 113 113 <div class="wp-booking-form-group"> 114 <label for="wp-booking-prop-adults"><?php esc_html_e( ' Capacidad Adultos *', 'bookingfacil-reservas' ); ?></label>114 <label for="wp-booking-prop-adults"><?php esc_html_e( 'Plazas para adultos *', 'bookingfacil-reservas' ); ?></label> 115 115 <input type="number" id="wp-booking-prop-adults" min="1" value="2" required /> 116 <small class="description"><?php esc_html_e( 'Los niños también pueden ocupar estas plazas.', 'bookingfacil-reservas' ); ?></small> 116 117 </div> 117 118 <div class="wp-booking-form-group"> 118 <label for="wp-booking-prop-children"><?php esc_html_e( ' Capacidad Niños', 'bookingfacil-reservas' ); ?></label>119 <label for="wp-booking-prop-children"><?php esc_html_e( 'Plazas adicionales solo para niños', 'bookingfacil-reservas' ); ?></label> 119 120 <input type="number" id="wp-booking-prop-children" min="0" value="0" /> 120 </div> 121 </div> 121 <small class="description"><?php esc_html_e( 'Dejalo en 0 si la unidad no tiene camas o cunas extra para niños.', 'bookingfacil-reservas' ); ?></small> 122 </div> 123 </div> 124 <p id="wp-booking-prop-capacity-total" class="description" style="margin:-4px 0 12px;font-weight:600;"></p> 122 125 123 126 <!-- ESPECIFICACIONES CLAVE: DORMITORIOS Y BAÑOS --> -
bookingfacil-reservas/trunk/readme.txt
r3738258 r3739702 5 5 Tested up to: 7.1.2 6 6 Requires PHP: 7.4 7 Stable tag: 1.3.2 27 Stable tag: 1.3.23 8 8 License: GPLv2 or later 9 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 88 88 == Changelog == 89 89 90 = 1.3.23 = 91 * Manual Booking Creation: allows lodging managers to register delayed or ongoing bookings with check-in dates prior to today, with an intuitive confirmation prompt. 92 * Automatic Email Suppression for Past Stays: automatically disables email delivery to guests if the manual stay has already finished. 93 * Interactive Unit Availability Cards: introduced interactive visual unit cards in manual reservations displaying live rates, available physical counts, and detailed blockage/unavailability reasons. 94 * Operator Availability Override: allows operators to proceed with booking registration under manual responsibility when units lack validated online inventory. 95 * Adult & Child Capacity Definitions: clarified adult bed capacities (which children can also occupy) vs. extra child-only beds/cribs with real-time total person counters in unit settings. 96 * Public Search Capacity Display: updated range recommendations and availability chips to state "Hasta X personas" including adult and child capacities. 97 90 98 = 1.3.22 = 91 99 * Frontdesk Daily Operations: added interactive confirmation alert prior to check-in registration to prevent accidental clicks. … … 252 260 == Upgrade Notice == 253 261 262 = 1.3.23 = 263 * Recommended update: enhances manual booking creation with delayed/past stay support, interactive unit availability cards, forced override under operator responsibility, and clarified adult/child capacity handling. 264 254 265 = 1.3.22 = 255 266 * Recommended update: enhances Frontdesk check-in workflow with interactive confirmation, automatic transfer to In-House Guests, reversible check-in, and clear visual distinction between vacant and occupied units in Housekeeping Express.
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)