/
home
/
simp499cl
/
public_html
/
prechequeo_v1
/
/home/simp499cl/public_html/prechequeo_v1
mkdir
upload
Name
Size
Mode
Actions
css/
-
0755
rm
font/
-
0755
rm
img/
-
0755
rm
js/
-
0755
rm
ttf/
-
0755
rm
agendar.html
98542
0644
edit
dl
rm
agendar10112025.html
42185
0644
edit
dl
rm
index.html
292492
0644
edit
dl
rm
Edit:
/home/simp499cl/public_html/prechequeo_v1/agendar.html
(98542B)
<!DOCTYPE html> <html lang="es_ES"> <head> <meta charset="utf-8"/> <meta content="width=device-width,initial-scale=1" name="viewport"/> <meta content="#000000" name="theme-color"/> <meta content="Agendar Paso 1" name="description"/> <title> Agendar Inspección PreCompra </title> <link href="css/main.css" rel="stylesheet"/> <link href="css/agendar.cleaned_formatted.css" rel="stylesheet"/> <style type="text/css"> [data-sonner-toaster][dir=ltr],html[dir=ltr]{--toast-icon-margin-start:-3px;--toast-icon-margin-end:4px;--toast-svg-margin-start:-1px;--toast-svg-margin-end:0px;--toast-button-margin-start:auto;--toast-button-margin-end:0;--toast-close-button-start:0;--toast-close-button-end:unset;--toast-close-button-transform:translate(-35%, -35%)}[data-sonner-toaster][dir=rtl],html[dir=rtl]{--toast-icon-margin-start:4px;--toast-icon-margin-end:-3px;--toast-svg-margin-start:0px;--toast-svg-margin-end:-1px;--toast-button-margin-start:0;--toast-button-margin-end:auto;--toast-close-button-start:unset;--toast-close-button-end:0;--toast-close-button-transform:translate(35%, -35%)}[data-sonner-toaster]{position:fixed;width:var(--width);font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;--gray1:hsl(0, 0%, 99%);--gray2:hsl(0, 0%, 97.3%);--gray3:hsl(0, 0%, 95.1%);--gray4:hsl(0, 0%, 93%);--gray5:hsl(0, 0%, 90.9%);--gray6:hsl(0, 0%, 88.7%);--gray7:hsl(0, 0%, 85.8%);--gray8:hsl(0, 0%, 78%);--gray9:hsl(0, 0%, 56.1%);--gray10:hsl(0, 0%, 52.3%);--gray11:hsl(0, 0%, 43.5%);--gray12:hsl(0, 0%, 9%);--border-radius:8px;box-sizing:border-box;padding:0;margin:0;list-style:none;outline:0;z-index:999999999;transition:transform .4s ease}@media (hover:none) and (pointer:coarse){[data-sonner-toaster][data-lifted=true]{transform:none}}[data-sonner-toaster][data-x-position=right]{right:var(--offset-right)}[data-sonner-toaster][data-x-position=left]{left:var(--offset-left)}[data-sonner-toaster][data-x-position=center]{left:50%;transform:translateX(-50%)}[data-sonner-toaster][data-y-position=top]{top:var(--offset-top)}[data-sonner-toaster][data-y-position=bottom]{bottom:var(--offset-bottom)}[data-sonner-toast]{--y:translateY(100%);--lift-amount:calc(var(--lift) * var(--gap));z-index:var(--z-index);position:absolute;opacity:0;transform:var(--y);touch-action:none;transition:transform .4s,opacity .4s,height .4s,box-shadow .2s;box-sizing:border-box;outline:0;overflow-wrap:anywhere}[data-sonner-toast][data-styled=true]{padding:16px;background:var(--normal-bg);border:1px solid var(--normal-border);color:var(--normal-text);border-radius:var(--border-radius);box-shadow:0 4px 12px rgba(0,0,0,.1);width:var(--width);font-size:13px;display:flex;align-items:center;gap:6px}[data-sonner-toast]:focus-visible{box-shadow:0 4px 12px rgba(0,0,0,.1),0 0 0 2px rgba(0,0,0,.2)}[data-sonner-toast][data-y-position=top]{top:0;--y:translateY(-100%);--lift:1;--lift-amount:calc(1 * var(--gap))}[data-sonner-toast][data-y-position=bottom]{bottom:0;--y:translateY(100%);--lift:-1;--lift-amount:calc(var(--lift) * var(--gap))}[data-sonner-toast][data-styled=true] [data-description]{font-weight:400;line-height:1.4;color:#3f3f3f}[data-rich-colors=true][data-sonner-toast][data-styled=true] [data-description]{color:inherit}[data-sonner-toaster][data-sonner-theme=dark] [data-description]{color:#e8e8e8}[data-sonner-toast][data-styled=true] [data-title]{font-weight:500;line-height:1.5;color:inherit}[data-sonner-toast][data-styled=true] [data-icon]{display:flex;height:16px;width:16px;position:relative;justify-content:flex-start;align-items:center;flex-shrink:0;margin-left:var(--toast-icon-margin-start);margin-right:var(--toast-icon-margin-end)}[data-sonner-toast][data-promise=true] [data-icon]>svg{opacity:0;transform:scale(.8);transform-origin:center;animation:sonner-fade-in .3s ease forwards}[data-sonner-toast][data-styled=true] [data-icon]>*{flex-shrink:0}[data-sonner-toast][data-styled=true] [data-icon] svg{margin-left:var(--toast-svg-margin-start);margin-right:var(--toast-svg-margin-end)}[data-sonner-toast][data-styled=true] [data-content]{display:flex;flex-direction:column;gap:2px}[data-sonner-toast][data-styled=true] [data-button]{border-radius:4px;padding-left:8px;padding-right:8px;height:24px;font-size:12px;color:var(--normal-bg);background:var(--normal-text);margin-left:var(--toast-button-margin-start);margin-right:var(--toast-button-margin-end);border:none;font-weight:500;cursor:pointer;outline:0;display:flex;align-items:center;flex-shrink:0;transition:opacity .4s,box-shadow .2s}[data-sonner-toast][data-styled=true] [data-button]:focus-visible{box-shadow:0 0 0 2px rgba(0,0,0,.4)}[data-sonner-toast][data-styled=true] [data-button]:first-of-type{margin-left:var(--toast-button-margin-start);margin-right:var(--toast-button-margin-end)}[data-sonner-toast][data-styled=true] [data-cancel]{color:var(--normal-text);background:rgba(0,0,0,.08)}[data-sonner-toaster][data-sonner-theme=dark] [data-sonner-toast][data-styled=true] [data-cancel]{background:rgba(255,255,255,.3)}[data-sonner-toast][data-styled=true] [data-close-button]{position:absolute;left:var(--toast-close-button-start);right:var(--toast-close-button-end);top:0;height:20px;width:20px;display:flex;justify-content:center;align-items:center;padding:0;color:var(--gray12);background:var(--normal-bg);border:1px solid var(--gray4);transform:var(--toast-close-button-transform);border-radius:50%;cursor:pointer;z-index:1;transition:opacity .1s,background .2s,border-color .2s}[data-sonner-toast][data-styled=true] [data-close-button]:focus-visible{box-shadow:0 4px 12px rgba(0,0,0,.1),0 0 0 2px rgba(0,0,0,.2)}[data-sonner-toast][data-styled=true] [data-disabled=true]{cursor:not-allowed}[data-sonner-toast][data-styled=true]:hover [data-close-button]:hover{background:var(--gray2);border-color:var(--gray5)}[data-sonner-toast][data-swiping=true]::before{content:'';position:absolute;left:-100%;right:-100%;height:100%;z-index:-1}[data-sonner-toast][data-y-position=top][data-swiping=true]::before{bottom:50%;transform:scaleY(3) translateY(50%)}[data-sonner-toast][data-y-position=bottom][data-swiping=true]::before{top:50%;transform:scaleY(3) translateY(-50%)}[data-sonner-toast][data-swiping=false][data-removed=true]::before{content:'';position:absolute;inset:0;transform:scaleY(2)}[data-sonner-toast][data-expanded=true]::after{content:'';position:absolute;left:0;height:calc(var(--gap) + 1px);bottom:100%;width:100%}[data-sonner-toast][data-mounted=true]{--y:translateY(0);opacity:1}[data-sonner-toast][data-expanded=false][data-front=false]{--scale:var(--toasts-before) * 0.05 + 1;--y:translateY(calc(var(--lift-amount) * var(--toasts-before))) scale(calc(-1 * var(--scale)));height:var(--front-toast-height)}[data-sonner-toast]>*{transition:opacity .4s}[data-sonner-toast][data-x-position=right]{right:0}[data-sonner-toast][data-x-position=left]{left:0}[data-sonner-toast][data-expanded=false][data-front=false][data-styled=true]>*{opacity:0}[data-sonner-toast][data-visible=false]{opacity:0;pointer-events:none}[data-sonner-toast][data-mounted=true][data-expanded=true]{--y:translateY(calc(var(--lift) * var(--offset)));height:var(--initial-height)}[data-sonner-toast][data-removed=true][data-front=true][data-swipe-out=false]{--y:translateY(calc(var(--lift) * -100%));opacity:0}[data-sonner-toast][data-removed=true][data-front=false][data-swipe-out=false][data-expanded=true]{--y:translateY(calc(var(--lift) * var(--offset) + var(--lift) * -100%));opacity:0}[data-sonner-toast][data-removed=true][data-front=false][data-swipe-out=false][data-expanded=false]{--y:translateY(40%);opacity:0;transition:transform .5s,opacity .2s}[data-sonner-toast][data-removed=true][data-front=false]::before{height:calc(var(--initial-height) + 20%)}[data-sonner-toast][data-swiping=true]{transform:var(--y) translateY(var(--swipe-amount-y,0)) translateX(var(--swipe-amount-x,0));transition:none}[data-sonner-toast][data-swiped=true]{user-select:none}[data-sonner-toast][data-swipe-out=true][data-y-position=bottom],[data-sonner-toast][data-swipe-out=true][data-y-position=top]{animation-duration:.2s;animation-timing-function:ease-out;animation-fill-mode:forwards}[data-sonner-toast][data-swipe-out=true][data-swipe-direction=left]{animation-name:swipe-out-left}[data-sonner-toast][data-swipe-out=true][data-swipe-direction=right]{animation-name:swipe-out-right}[data-sonner-toast][data-swipe-out=true][data-swipe-direction=up]{animation-name:swipe-out-up}[data-sonner-toast][data-swipe-out=true][data-swipe-direction=down]{animation-name:swipe-out-down}@keyframes swipe-out-left{from{transform:var(--y) translateX(var(--swipe-amount-x));opacity:1}to{transform:var(--y) translateX(calc(var(--swipe-amount-x) - 100%));opacity:0}}@keyframes swipe-out-right{from{transform:var(--y) translateX(var(--swipe-amount-x));opacity:1}to{transform:var(--y) translateX(calc(var(--swipe-amount-x) + 100%));opacity:0}}@keyframes swipe-out-up{from{transform:var(--y) translateY(var(--swipe-amount-y));opacity:1}to{transform:var(--y) translateY(calc(var(--swipe-amount-y) - 100%));opacity:0}}@keyframes swipe-out-down{from{transform:var(--y) translateY(var(--swipe-amount-y));opacity:1}to{transform:var(--y) translateY(calc(var(--swipe-amount-y) + 100%));opacity:0}}@media (max-width:600px){[data-sonner-toaster]{position:fixed;right:var(--mobile-offset-right);left:var(--mobile-offset-left);width:100%}[data-sonner-toaster][dir=rtl]{left:calc(var(--mobile-offset-left) * -1)}[data-sonner-toaster] [data-sonner-toast]{left:0;right:0;width:calc(100% - var(--mobile-offset-left) * 2)}[data-sonner-toaster][data-x-position=left]{left:var(--mobile-offset-left)}[data-sonner-toaster][data-y-position=bottom]{bottom:var(--mobile-offset-bottom)}[data-sonner-toaster][data-y-position=top]{top:var(--mobile-offset-top)}[data-sonner-toaster][data-x-position=center]{left:var(--mobile-offset-left);right:var(--mobile-offset-right);transform:none}}[data-sonner-toaster][data-sonner-theme=light]{--normal-bg:#fff;--normal-border:var(--gray4);--normal-text:var(--gray12);--success-bg:hsl(143, 85%, 96%);--success-border:hsl(145, 92%, 87%);--success-text:hsl(140, 100%, 27%);--info-bg:hsl(208, 100%, 97%);--info-border:hsl(221, 91%, 93%);--info-text:hsl(210, 92%, 45%);--warning-bg:hsl(49, 100%, 97%);--warning-border:hsl(49, 91%, 84%);--warning-text:hsl(31, 92%, 45%);--error-bg:hsl(359, 100%, 97%);--error-border:hsl(359, 100%, 94%);--error-text:hsl(360, 100%, 45%)}[data-sonner-toaster][data-sonner-theme=light] [data-sonner-toast][data-invert=true]{--normal-bg:#000;--normal-border:hsl(0, 0%, 20%);--normal-text:var(--gray1)}[data-sonner-toaster][data-sonner-theme=dark] [data-sonner-toast][data-invert=true]{--normal-bg:#fff;--normal-border:var(--gray3);--normal-text:var(--gray12)}[data-sonner-toaster][data-sonner-theme=dark]{--normal-bg:#000;--normal-bg-hover:hsl(0, 0%, 12%);--normal-border:hsl(0, 0%, 20%);--normal-border-hover:hsl(0, 0%, 25%);--normal-text:var(--gray1);--success-bg:hsl(150, 100%, 6%);--success-border:hsl(147, 100%, 12%);--success-text:hsl(150, 86%, 65%);--info-bg:hsl(215, 100%, 6%);--info-border:hsl(223, 43%, 17%);--info-text:hsl(216, 87%, 65%);--warning-bg:hsl(64, 100%, 6%);--warning-border:hsl(60, 100%, 9%);--warning-text:hsl(46, 87%, 65%);--error-bg:hsl(358, 76%, 10%);--error-border:hsl(357, 89%, 16%);--error-text:hsl(358, 100%, 81%)}[data-sonner-toaster][data-sonner-theme=dark] [data-sonner-toast] [data-close-button]{background:var(--normal-bg);border-color:var(--normal-border);color:var(--normal-text)}[data-sonner-toaster][data-sonner-theme=dark] [data-sonner-toast] [data-close-button]:hover{background:var(--normal-bg-hover);border-color:var(--normal-border-hover)}[data-rich-colors=true][data-sonner-toast][data-type=success]{background:var(--success-bg);border-color:var(--success-border);color:var(--success-text)}[data-rich-colors=true][data-sonner-toast][data-type=success] [data-close-button]{background:var(--success-bg);border-color:var(--success-border);color:var(--success-text)}[data-rich-colors=true][data-sonner-toast][data-type=info]{background:var(--info-bg);border-color:var(--info-border);color:var(--info-text)}[data-rich-colors=true][data-sonner-toast][data-type=info] [data-close-button]{background:var(--info-bg);border-color:var(--info-border);color:var(--info-text)}[data-rich-colors=true][data-sonner-toast][data-type=warning]{background:var(--warning-bg);border-color:var(--warning-border);color:var(--warning-text)}[data-rich-colors=true][data-sonner-toast][data-type=warning] [data-close-button]{background:var(--warning-bg);border-color:var(--warning-border);color:var(--warning-text)}[data-rich-colors=true][data-sonner-toast][data-type=error]{background:var(--error-bg);border-color:var(--error-border);color:var(--error-text)}[data-rich-colors=true][data-sonner-toast][data-type=error] [data-close-button]{background:var(--error-bg);border-color:var(--error-border);color:var(--error-text)}.sonner-loading-wrapper{--size:16px;height:var(--size);width:var(--size);position:absolute;inset:0;z-index:10}.sonner-loading-wrapper[data-visible=false]{transform-origin:center;animation:sonner-fade-out .2s ease forwards}.sonner-spinner{position:relative;top:50%;left:50%;height:var(--size);width:var(--size)}.sonner-loading-bar{animation:sonner-spin 1.2s linear infinite;background:var(--gray11);border-radius:6px;height:8%;left:-10%;position:absolute;top:-3.9%;width:24%}.sonner-loading-bar:first-child{animation-delay:-1.2s;transform:rotate(.0001deg) translate(146%)}.sonner-loading-bar:nth-child(2){animation-delay:-1.1s;transform:rotate(30deg) translate(146%)}.sonner-loading-bar:nth-child(3){animation-delay:-1s;transform:rotate(60deg) translate(146%)}.sonner-loading-bar:nth-child(4){animation-delay:-.9s;transform:rotate(90deg) translate(146%)}.sonner-loading-bar:nth-child(5){animation-delay:-.8s;transform:rotate(120deg) translate(146%)}.sonner-loading-bar:nth-child(6){animation-delay:-.7s;transform:rotate(150deg) translate(146%)}.sonner-loading-bar:nth-child(7){animation-delay:-.6s;transform:rotate(180deg) translate(146%)}.sonner-loading-bar:nth-child(8){animation-delay:-.5s;transform:rotate(210deg) translate(146%)}.sonner-loading-bar:nth-child(9){animation-delay:-.4s;transform:rotate(240deg) translate(146%)}.sonner-loading-bar:nth-child(10){animation-delay:-.3s;transform:rotate(270deg) translate(146%)}.sonner-loading-bar:nth-child(11){animation-delay:-.2s;transform:rotate(300deg) translate(146%)}.sonner-loading-bar:nth-child(12){animation-delay:-.1s;transform:rotate(330deg) translate(146%)}@keyframes sonner-fade-in{0%{opacity:0;transform:scale(.8)}100%{opacity:1;transform:scale(1)}}@keyframes sonner-fade-out{0%{opacity:1;transform:scale(1)}100%{opacity:0;transform:scale(.8)}}@keyframes sonner-spin{0%{opacity:1}100%{opacity:.15}}@media (prefers-reduced-motion){.sonner-loading-bar,[data-sonner-toast],[data-sonner-toast]>*{transition:none!important;animation:none!important}}.sonner-loader{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);transform-origin:center;transition:opacity .2s,transform .2s}.sonner-loader[data-visible=false]{opacity:0;transform:scale(.8) translate(-50%,-50%)} </style> <link href="img/cropped-favicon-32x32.webp" rel="icon" sizes="32x32"/> <link href="img/cropped-favicon-192x192.webp" rel="icon" sizes="192x192"/> <link href="img/cropped-favicon-180x180.webp" rel="apple-touch-icon"/> <meta content="img/cropped-favicon-270x270.webp" name="msapplication-TileImage"/> <style>/* Utilidad para ocultar visualmente manteniendo accesibilidad */ /* accesibilidad */ .sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0; } /* Opciones: blanco, sin líneas, padding y bordes suaves */ #comuna-menu [role="option"] { background-color: #ffffff; border: none; border-radius: 0.375rem; /* rounded-md */ padding: 0.5rem 0.75rem; /* px-3 py-2 */ cursor: pointer; transition: background-color 0.2s ease; display: flex; align-items: center; justify-content: space-between; } /* Hover: bg-blue-50 */ #comuna-menu [role="option"]:hover { background-color: #eff6ff; /* bg-blue-50 */ } /* Seleccionado: mantiene bg-blue-50 y muestra el check */ #comuna-menu [role="option"][data-selected="true"] { background-color: #eff6ff; /* bg-blue-50 */ } /* Monto SIEMPRE azul (en la lista y en el trigger) */ #comuna-menu [role="option"] .option-line .monto { color: #1d4ed8; /* text-blue-500 */ font-weight: 600; } /* Ícono check (oculto por defecto, visible en seleccionado) */ #comuna-menu [role="option"] .check { display: none; } #comuna-menu [role="option"][data-selected="true"] .check { display: inline-flex; } /* Scroll del menú de comunas */ #comuna-menu { max-height: 250px; /* desktop */ scrollbar-width: thin; scrollbar-color: #c7d2fe transparent; /* azul clarito */ } /* Scroll más alto en mobile */ @media (max-width: 768px) { #comuna-menu { max-height: 60vh; } } /* Personaliza el scrollbar en navegadores WebKit */ #comuna-menu::-webkit-scrollbar { width: 6px; } #comuna-menu::-webkit-scrollbar-thumb { background-color: #c7d2fe; /* azul suave */ border-radius: 9999px; } #comuna-menu::-webkit-scrollbar-track { background: transparent; } </style> <style> /* Bottom sheet móvil */ @media (max-width: 1023px) { .mobile-sheet { position: fixed; left: 0; right: 0; bottom: 0; background: #fff; border-top-left-radius: 16px; border-top-right-radius: 16px; box-shadow: 0 -10px 30px rgba(0,0,0,.15); transform: translateY(100%); transition: transform .35s ease; z-index: 60; /* sobre el botón */ } .mobile-sheet.open { transform: translateY(0); } .mobile-sheet__handle { width: 42px; height: 5px; border-radius: 9999px; background: #E5E7EB; margin: 8px auto 12px; } .mobile-sheet__content { max-height: 70vh; overflow: auto; padding: 12px 16px 16px; } .mobile-sheet-backdrop { position: fixed; inset: 0; background: rgba(0,0,0,.35); opacity: 0; pointer-events: none; transition: opacity .25s ease; z-index: 50; } .mobile-sheet-backdrop.open { opacity: 1; pointer-events: auto; } } </style> </head> <body> <noscript> You need to enable JavaScript to run this app. </noscript> <div id="root" > <div class="App bg-gray-100"> <div class="min-h-screen py-8 px-4 sm:px-6 lg:px-8"> <div class="max-w-7xl mx-auto"> <div class="text-center mb-12"> <h1 class="text-4xl sm:text-5xl font-bold text-gray-600 mb-4" data-testid="booking-title"> Agenda Ahora </h1> <p class="text-lg text-gray-600"> Reserva de forma rápida y segura tu Inspección PreCompra </p> </div> <div class="mb-12"> <div class="flex items-center justify-center space-x-4"> <div class="flex flex-col items-center cursor-pointer transition-all opacity-100" data-testid="step-indicator-1"> <div id="step1Icon" class="w-16 h-16 rounded-full flex items-center justify-center mb-2 transition-all bg-[rgb(16,113,229)] text-white ring-4 ring-blue-200"> <svg aria-hidden="true" class="lucide lucide-file-text w-8 h-8" fill="none" height="24" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewbox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"> <path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"> </path> <path d="M14 2v4a2 2 0 0 0 2 2h4"> </path> <path d="M10 9H8"> </path> <path d="M16 13H8"> </path> <path d="M16 17H8"> </path> </svg> </div> <span class="text-xs sm:text-sm font-medium text-gray-700 text-center max-w-[80px]"> Fecha </span> </div> <div id="connector1" class="w-12 sm:w-24 h-1 rounded bg-gray-200"> </div> <div id="step2IconOpacity" class="flex flex-col items-center cursor-pointer transition-all opacity-40 cursor-not-allowed" data-testid="step-indicator-2"> <div id="step2Icon" class="w-16 h-16 rounded-full flex items-center justify-center mb-2 transition-all bg-gray-200 text-gray-500"> <svg aria-hidden="true" class="lucide lucide-map-pin w-8 h-8" fill="none" height="24" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewbox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"> <path d="M20 10c0 4.993-5.539 10.193-7.399 11.799a1 1 0 0 1-1.202 0C9.539 20.193 4 14.993 4 10a8 8 0 0 1 16 0"> </path> <circle cx="12" cy="10" r="3"> </circle> </svg> </div> <span class="text-xs sm:text-sm font-medium text-gray-700 text-center max-w-[80px]"> Dirección </span> </div> <div id="connector2" class="w-12 sm:w-24 h-1 rounded bg-gray-200"> </div> <div id="step3IconOpacity" class="flex flex-col items-center cursor-pointer transition-all opacity-40 cursor-not-allowed" data-testid="step-indicator-3"> <div id="step3Icon" class="w-16 h-16 rounded-full flex items-center justify-center mb-2 transition-all bg-gray-200 text-gray-500"> <svg aria-hidden="true" class="lucide lucide-car w-8 h-8" fill="none" height="24" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewbox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"> <path d="M19 17h2c.6 0 1-.4 1-1v-3c0-.9-.7-1.7-1.5-1.9C18.7 10.6 16 10 16 10s-1.3-1.4-2.2-2.3c-.5-.4-1.1-.7-1.8-.7H5c-.6 0-1.1.4-1.4.9l-1.4 2.9A3.7 3.7 0 0 0 2 12v4c0 .6.4 1 1 1h2"> </path> <circle cx="7" cy="17" r="2"> </circle> <path d="M9 17h6"> </path> <circle cx="17" cy="17" r="2"> </circle> </svg> </div> <span class="text-xs sm:text-sm font-medium text-gray-700 text-center max-w-[80px]"> Vehículo </span> </div> <div id="connector3" class="w-12 sm:w-24 h-1 rounded bg-gray-200"> </div> <div id="step4IconOpacity" class="flex flex-col items-center cursor-pointer transition-all opacity-40 cursor-not-allowed" data-testid="step-indicator-4"> <div id="step4Icon" class="w-16 h-16 rounded-full flex items-center justify-center mb-2 transition-all bg-gray-200 text-gray-500"> <svg aria-hidden="true" class="lucide lucide-user w-8 h-8" fill="none" height="24" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewbox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"> <path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"> </path> <circle cx="12" cy="7" r="4"> </circle> </svg> </div> <span class="text-xs sm:text-sm font-medium text-gray-700 text-center max-w-[80px]"> Cliente </span> </div> </div> </div> <div class="grid grid-cols-1 lg:grid-cols-3 gap-8"> <div class="lg:col-span-2"> <div class="bg-white rounded-2xl shadow-lg p-6 sm:p-8" data-testid="step-1-form"> <div class="space-y-8"> <!-- FECHA Y HORA (DINÁMICO) --> <div> <h3 class="text-lg font-semibold text-gray-600 mb-4"> ¿Cuándo deseas realizar la inspección del vehículo? </h3> <div class="flex items-center space-x-2"> <button id="btnPrev" class="p-2 rounded-full hover:bg-gray-100 disabled:opacity-30 disabled:cursor-not-allowed" aria-label="Anterior" disabled> <svg class="lucide lucide-chevron-left w-6 h-6 text-gray-600" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <path d="m15 18-6-6 6-6"></path> </svg> </button> <div id="dateCarousel" class="flex-1 grid grid-cols-2 sm:grid-cols-5 gap-2 justify-items-center sm:justify-items-stretch max-w-[320px] sm:max-w-none mx-auto sm:mx-0"> </div> <button id="btnNext" class="p-2 rounded-full hover:bg-gray-100 disabled:opacity-30 disabled:cursor-not-allowed" aria-label="Siguiente"> <svg class="lucide lucide-chevron-right w-6 h-6 text-gray-600" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <path d="m9 18 6-6-6-6"></path> </svg> </button> </div> </div> <div class="mt-8"> <h3 class="text-lg font-semibold text-gray-600 mb-4 flex items-center"> ¿A qué hora deseas realizar la inspección? </h3> <!-- Horas disponibles --> <div id="hoursGrid" class="grid grid-cols-3 sm:grid-cols-5 md:grid-cols-9 gap-2"> <!-- Rellenado por JS --> </div> </div> <div class="flex justify-end pt-6"> <button class="inline-flex items-center justify-center gap-2 whitespace-nowrap font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 shadow h-9 bg-[rgb(16,113,229)] hover:bg-blue-700 text-white px-8 py-6 text-lg rounded-lg" data-testid="step1-next-btn"> Siguiente </button> </div> </div> </div> </div> <!-- PASO 2: Dirección (oculto al inicio) --> <div id="step2" class="hidden lg:col-span-2"> <div class="bg-white rounded-2xl shadow-lg p-6 sm:p-8"> <div class="space-y-8"> <h3 class="text-lg font-semibold text-gray-600 mb-4"> ¿Donde se encuentra el vehículo? </h3> <!-- Región (placeholder fija para ahora) --> <div class=""> <label class="text-sm font-semibold text-gray-700 mb-2">Región</label> <div class="p-4 bg-gray-50 rounded-lg border-2 border-gray-200"> <div class="flex items-center"> <svg xmlns="http://www.w3.org/2000/svg" class="lucide lucide-map-pin w-5 h-5 text-[rgb(16,113,229)] mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"> <path d="M20 10c0 4.993-5.539 10.193-7.399 11.799a1 1 0 0 1-1.202 0C9.539 20.193 4 14.993 4 10a8 8 0 0 1 16 0"></path> <circle cx="12" cy="10" r="3"></circle> </svg> <span class="font-medium text-gray-900">Región Metropolitana</span> </div> </div> </div> <!-- Comuna (dropdown custom con icono, monto azul y check en seleccionado) --> <div class="relative" id="comuna-dropdown"> <label for="comuna-select" class="peer-disabled:cursor-not-allowed peer-disabled:opacity-70 text-sm font-semibold text-gray-700 mb-2 block"> Comuna </label> <!-- Select oculto (submit / compat) --> <select id="comuna-select" class="sr-only" tabindex="-1" aria-hidden="true"> <option value=""><span>Selecciona una comuna</span></option> </select> <!-- Trigger --> <button type="button" id="comuna-trigger" class="flex items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 shadow-sm ring-offset-background data-[placeholder]:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1 w-full h-14 text-base"> <span id="comuna-trigger-label" class="truncate text-gray-500">Selecciona una comuna</span> <span class="flex items-center gap-2 pointer-events-none"> <span id="comuna-trigger-amount" class="text-blue-500 font-semibold hidden"></span> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-down h-4 w-4 opacity-50" aria-hidden="true"> <path d="m6 9 6 6 6-6"></path> </svg> </span> </button> <div id="comuna-menu" role="listbox" tabindex="-1" aria-labelledby="comuna-trigger" class="hidden absolute z-[9999] mt-1 w-full rounded-md bg-white shadow-xl p-1 overflow-y-auto"> </div> <!-- Hint costo --> <p id="comuna-costo-help" class="text-xs text-gray-500 mt-2 hidden"> Desplazamiento: <span id="comuna-costo-text">$0</span> <span id="resumen-comuna-nombre" class="text-gray-400"></span> </p> <!-- Dirección exacta (no se agrega al resumen) --> <div class="mt-4"> <label for="direccion-exacta" class="peer-disabled:cursor-not-allowed peer-disabled:opacity-70 text-sm font-semibold text-gray-700 mb-2 block"> Dirección exacta </label> <div class="relative"> <input id="direccion-exacta" name="direccionExacta" type="text" inputmode="text" autocomplete="street-address" placeholder="Ej: Av. Apoquindo 3500, depto 1201, Las Condes" class="w-full h-14 text-base rounded-md border border-input bg-white px-3 pr-10 py-2 shadow-sm focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50" data-testid="direccion-exacta-input" /> <!-- Icono de ubicación (decorativo) --> <span class="pointer-events-none absolute right-3 inset-y-0 flex items-center text-gray-400"> <svg xmlns="http://www.w3.org/2000/svg" class="lucide lucide-map-pin h-5 w-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"> <path d="M20 10c0 4.993-5.539 10.193-7.399 11.799a1 1 0 0 1-1.202 0C9.539 20.193 4 14.993 4 10a8 8 0 0 1 16 0"/> <circle cx="12" cy="10" r="3"/> </svg> </span> </div> <p class="text-xs text-gray-500 mt-2"> Ingresa calle y número; referencias opcionales (no se mostrará en el resumen). </p> </div> </div> <script> (function(){ const COMUNAS = window.COMUNAS || [ { nombre:'Santiago', costo:8000 }, { nombre:'Providencia', costo:12000 }, { nombre:'Ñuñoa', costo:12000 }, { nombre:'Independencia', costo:10000 }, { nombre:'Recoleta', costo:10000 }, { nombre:'Conchalí', costo:11000 }, { nombre:'Huechuraba', costo:13000 }, { nombre:'Quilicura', costo:14000 }, { nombre:'Renca', costo:11000 }, { nombre:'Cerro Navia', costo:11000 }, { nombre:'Lo Prado', costo:11000 }, { nombre:'Quinta Normal', costo:10000 }, { nombre:'Estación Central', costo:10000 }, { nombre:'Maipú', costo:16000 }, { nombre:'Pudahuel', costo:15000 }, { nombre:'Las Condes', costo:15000 }, { nombre:'Vitacura', costo:16000 }, { nombre:'Lo Barnechea', costo:18000 } ]; const fmtCLP = n => Number(n||0).toLocaleString('es-CL',{style:'currency',currency:'CLP',maximumFractionDigits:0}); const $ = s => document.querySelector(s); const $$ = s => Array.from(document.querySelectorAll(s)); const sel = $('#comuna-select'); const trigger = $('#comuna-trigger'); const label = $('#comuna-trigger-label'); const menu = $('#comuna-menu'); let selectedName = ''; // track selección para reabrir con highlight if (typeof window.costoDesplaz === 'undefined') window.costoDesplaz = 0; function runUpdateResumen(){ if (typeof window.updateResumen === 'function') return window.updateResumen(); if (typeof window.updateSummary === 'function') return window.updateSummary(); if (typeof window._updateSummary === 'function') return window._updateSummary(); } function setDesplazamiento(monto, nombreComuna){ window.costoDesplaz = Number(monto)||0; const elDespl = document.getElementById('resumen-desplazamiento'); if (elDespl) elDespl.textContent = fmtCLP(window.costoDesplaz); const elNombre = document.getElementById('resumen-comuna-nombre'); if (elNombre) elNombre.textContent = nombreComuna ? `(${nombreComuna})` : ''; const help = document.getElementById('comuna-costo-help'); const helpVal = document.getElementById('comuna-costo-text'); if (help && helpVal){ help.classList.remove('hidden'); helpVal.textContent = fmtCLP(window.costoDesplaz); } runUpdateResumen(); if (typeof window.syncMobileSummary === 'function') window.syncMobileSummary(); } function populate(){ // limpia sel.querySelectorAll('option:not([value=""])').forEach(o=>o.remove()); menu.innerHTML = ''; COMUNAS.forEach(({nombre, costo})=>{ // option oculto (compat) const opt = document.createElement('option'); opt.value = nombre; opt.dataset.costo = String(costo); sel.appendChild(opt); // opción visual: // - nombre y monto (monto azul) JUNTOS en la misma línea // - check a la derecha (solo si seleccionado) const item = document.createElement('div'); item.setAttribute('role','option'); item.dataset.value = nombre; item.dataset.costo = String(costo); item.innerHTML = ` <div class="option-line truncate"> <span class="nombre text-gray-800">${nombre}</span> <span class="monto ml-1">+${fmtCLP(costo)}</span> </div> <span class="check text-blue-600"> <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"> <path d="M20 6 9 17l-5-5"></path> </svg> </span> `; item.addEventListener('click', ()=> selectValue(nombre, costo)); menu.appendChild(item); }); } function markSelectedInMenu(){ // aplica highlight y muestra check en la opción coincidente const items = $$('#comuna-menu [role="option"]'); items.forEach(it => { if (it.dataset.value === selectedName) { it.setAttribute('data-selected','true'); } else { it.removeAttribute('data-selected'); } }); } function selectValue(nombre, costo){ selectedName = nombre; window.selectedComunaName = nombre; // actualiza select oculto y trigger sel.value = nombre; label.innerHTML = `${nombre} <span class="text-blue-500 font-semibold">+${fmtCLP(costo)}</span>`; setDesplazamiento(costo, nombre); closeMenu(); } const dropdown = document.getElementById('comuna-dropdown'); // placeholder para poder devolver el menú a su lugar original al cerrar let placeholder = null; let isOpen = false; function positionMenu() { const r = trigger.getBoundingClientRect(); // ancho igual al trigger; 8px de separación vertical (mt-2) Object.assign(menu.style, { position: 'fixed', top: (r.bottom + 8) + 'px', left: r.left + 'px', width: r.width + 'px', zIndex: 99999, maxHeight: '60vh', // scroll cómodo en mobile overflowY: 'auto', borderRadius: '0.5rem', boxShadow: '0 20px 25px -5px rgba(0,0,0,.1), 0 10px 10px -5px rgba(0,0,0,.04)', background: '#fff', padding: '0.25rem' }); } function openMenu() { if (isOpen) return; isOpen = true; // crea placeholder y mueve el menú al body (portal) placeholder = document.createElement('span'); placeholder.style.display = 'none'; menu.after(placeholder); document.body.appendChild(menu); menu.classList.remove('hidden'); positionMenu(); markSelectedInMenu(); // al abrir, resaltar la opción seleccionada // listeners para re-posicionar window.addEventListener('resize', positionMenu); window.addEventListener('scroll', positionMenu, true); // true: captura scroll en contenedores document.addEventListener('click', onDocClick); } function closeMenu() { if (!isOpen) return; isOpen = false; menu.classList.add('hidden'); // devuelve el menú a su sitio original if (placeholder && placeholder.parentNode) { placeholder.replaceWith(menu); placeholder = null; } // limpia estilos inline para no interferir cuando está embebido menu.removeAttribute('style'); window.removeEventListener('resize', positionMenu); window.removeEventListener('scroll', positionMenu, true); document.removeEventListener('click', onDocClick); } function onDocClick(e) { // cierra si el click no está en trigger ni en el propio menú if (!trigger.contains(e.target) && !menu.contains(e.target)) { closeMenu(); } } // Toggle trigger.addEventListener('click', () => (isOpen ? closeMenu() : openMenu())); // Exponer para tu código actual si lo necesita window.__comunaMenuOpen = openMenu; window.__comunaMenuClose = closeMenu; // Init populate(); // Preselección por QS ?comuna=Ñuñoa const qs = new URLSearchParams(location.search); const pre = qs.get('comuna'); if (pre){ const found = COMUNAS.find(c=>c.nombre===pre); if (found){ selectedName = found.nombre; selectValue(found.nombre, found.costo); } } })(); </script> <!-- Navegación Paso 2 (puedes ajustar ids y handlers luego) --> <div class="flex justify-between pt-6"> <button type="button" id="step2BackBtn" class="inline-flex items-center justify-center gap-2 border shadow-sm h-9 px-8 py-6 text-lg rounded-lg"> Atrás </button> <button type="button" id="step2NextBtn" class="inline-flex items-center justify-center gap-2 shadow h-9 bg-[rgb(16,113,229)] hover:bg-blue-700 text-white px-8 py-6 text-lg rounded-lg"> Siguiente </button> </div> </div> </div> </div> <!-- PASO 3: Datos del vehículo (oculto al inicio) --> <div id="step3" class="hidden lg:col-span-2"> <div class="bg-white rounded-2xl shadow-lg p-6 sm:p-8"> <div class="space-y-8"> <!-- Encabezado --> <div> <h2 class="text-2xl sm:text-3xl font-bold text-gray-900 mb-2">Datos del Vehículo</h2> <p class="text-gray-600"> Ingresa la información del vehículo que deseas inspeccionar. Si no eres el propietario, indícalo para poder coordinar correctamente la visita. </p> </div> <!-- Campos del vehículo --> <div class="grid grid-cols-1 md:grid-cols-2 gap-4"> <div> <label class="text-sm font-semibold text-gray-700 mb-2 block">Marca (opcional)</label> <input type="text" id="veh-marca" placeholder="Ej: Toyota" class="w-full h-14 text-base rounded-md border bg-white px-3 py-2 shadow-sm focus:outline-none focus:ring-1 focus:ring-ring" /> </div> <div> <label class="text-sm font-semibold text-gray-700 mb-2 block">Modelo (opcional)</label> <input type="text" id="veh-modelo" placeholder="Ej: Corolla" class="w-full h-14 text-base rounded-md border bg-white px-3 py-2 shadow-sm focus:outline-none focus:ring-1 focus:ring-ring" /> </div> <div> <label class="text-sm font-semibold text-gray-700 mb-2 block">Año (opcional)</label> <input type="number" id="veh-anio" placeholder="Ej: 2018" min="1900" max="2100" inputmode="numeric" class="w-full h-14 text-base rounded-md border bg-white px-3 py-2 shadow-sm focus:outline-none focus:ring-1 focus:ring-ring" /> </div> <div> <label class="text-sm font-semibold text-gray-700 mb-2 block">Patente (opcional)</label> <input type="text" id="veh-patente" placeholder="Ej: ABCD-12" class="w-full h-14 text-base rounded-md border bg-white px-3 py-2 shadow-sm focus:outline-none focus:ring-1 focus:ring-ring" /> </div> <div> <label class="text-sm font-semibold text-gray-700 mb-2 block">Color (opcional)</label> <input type="text" id="veh-color" placeholder="Ej: Gris" class="w-full h-14 text-base rounded-md border bg-white px-3 py-2 shadow-sm focus:outline-none focus:ring-1 focus:ring-ring" /> </div> </div> <!-- Check propietario --> <div class="mt-6"> <label class="inline-flex items-center gap-3"> <input type="checkbox" id="check-propietario" checked class="w-5 h-5 text-blue-600 border rounded focus:ring-blue-500" /> <span class="text-gray-700 font-medium p-2">Soy el propietario del vehículo</span> </label> </div> <!-- Datos del responsable (ocultos por defecto) --> <div id="responsable-block" class=" hidden bg-blue-50 p-4 rounded-lg"> <div class="flex justify-between items-center mb-2"> <div class="mt-4 space-y-3"> <p class="text-sm text-gray-600"> Esta información es necesaria para coordinar la visita en caso de que el propietario no esté presente. </p> <div class="grid grid-cols-1 md:grid-cols-2 gap-4"> <div> <label class="text-sm font-semibold text-gray-700 mb-2 block">Nombre del responsable</label> <input type="text" id="resp-nombre" placeholder="Ej: Pedro Gómez" class="w-full h-14 text-base rounded-md border bg-white px-3 py-2 shadow-sm focus:outline-none focus:ring-1 focus:ring-ring" /> </div> <div> <label class="text-sm font-semibold text-gray-700 mb-2 block">Teléfono del responsable</label> <input type="tel" id="resp-telefono" placeholder="+56 9 8765 4321" class="w-full h-14 text-base rounded-md border bg-white px-3 py-2 shadow-sm focus:outline-none focus:ring-1 focus:ring-ring" /> </div> </div> </div> </div> </div> <!-- Navegación Paso 3 --> <div class="flex justify-between pt-6"> <button type="button" id="step3BackBtn" class="inline-flex items-center justify-center gap-2 border shadow-sm h-9 px-8 py-6 text-lg rounded-lg"> Atrás </button> <button type="button" id="step3NextBtn" class="inline-flex items-center justify-center gap-2 shadow h-9 bg-[rgb(16,113,229)] hover:bg-blue-700 text-white px-8 py-6 text-lg rounded-lg"> Siguiente </button> </div> </div> </div> </div> <!-- Script de comportamiento del check --> <script> (function(){ const check = document.getElementById('check-propietario'); const block = document.getElementById('responsable-block'); if (!check || !block) return; function toggleResponsable() { if (check.checked) { block.classList.add('hidden'); } else { block.classList.remove('hidden'); } } check.addEventListener('change', toggleResponsable); toggleResponsable(); // inicial })(); </script> <!-- PASO 4: Datos del cliente (oculto al inicio) --> <div id="step4" class="hidden lg:col-span-2"> <div class="bg-white rounded-2xl shadow-lg p-6 sm:p-8"> <div class="space-y-8"> <div> <h2 class="text-2xl sm:text-3xl font-bold text-gray-900 mb-2">Datos del Cliente</h2> <p class="text-gray-600"> Necesitamos tus datos de contacto para confirmar la reserva. Si requieres factura, completa los datos de facturación. </p> </div> <!-- Datos de contacto --> <div class="grid grid-cols-1 sm:grid-cols-3 gap-4"> <div> <label class="text-sm font-semibold text-gray-700 mb-2 block">Nombre</label> <input type="text" id="cli-nombre" placeholder="Ej: Juan Pérez" class="w-full h-14 text-base rounded-md border bg-white px-3 py-2 shadow-sm focus:outline-none focus:ring-1 focus:ring-ring" /> </div> <div> <label class="text-sm font-semibold text-gray-700 mb-2 block">Email</label> <input type="email" id="cli-email" placeholder="tu@email.com" class="w-full h-14 text-base rounded-md border bg-white px-3 py-2 shadow-sm focus:outline-none focus:ring-1 focus:ring-ring" /> </div> <div> <label class="text-sm font-semibold text-gray-700 mb-2 block">Teléfono</label> <input type="tel" id="cli-telefono" placeholder="+56 9 1234 5678" class="w-full h-14 text-base rounded-md border bg-white px-3 py-2 shadow-sm focus:outline-none focus:ring-1 focus:ring-ring" /> </div> </div> <!-- Boleta / Factura --> <div class="space-y-3"> <span class="text-sm font-semibold text-gray-700 block">Documento tributario</span> <div class="flex items-center gap-6"> <label class="inline-flex items-center gap-2"> <input type="radio" name="docTipo" value="boleta" id="doc-boleta" class="h-4 w-4" checked /> <span>Boleta</span> </label> <label class="inline-flex items-center gap-2"> <input type="radio" name="docTipo" value="factura" id="doc-factura" class="h-4 w-4" /> <span>Factura</span> </label> </div> </div> <!-- Datos de Facturación (condicional) --> <div id="factura-block" class="hidden space-y-4"> <h3 class="text-lg font-semibold text-gray-900">Datos de facturación</h3> <div class="grid grid-cols-1 sm:grid-cols-2 gap-4"> <div> <label class="text-sm font-semibold text-gray-700 mb-2 block">RUT</label> <input type="text" id="fac-rut" placeholder="76.123.456-7" class="w-full h-14 text-base rounded-md border bg-white px-3 py-2 shadow-sm focus:outline-none focus:ring-1 focus:ring-ring" /> </div> <div> <label class="text-sm font-semibold text-gray-700 mb-2 block">Razón social</label> <input type="text" id="fac-razon" placeholder="Empresa S.A." class="w-full h-14 text-base rounded-md border bg-white px-3 py-2 shadow-sm focus:outline-none focus:ring-1 focus:ring-ring" /> </div> <div> <label class="text-sm font-semibold text-gray-700 mb-2 block">Giro</label> <input type="text" id="fac-giro" placeholder="Comercio al por menor" class="w-full h-14 text-base rounded-md border bg-white px-3 py-2 shadow-sm focus:outline-none focus:ring-1 focus:ring-ring" /> </div> <div> <label class="text-sm font-semibold text-gray-700 mb-2 block">Email de facturación</label> <input type="email" id="fac-email" placeholder="facturas@empresa.cl" class="w-full h-14 text-base rounded-md border bg-white px-3 py-2 shadow-sm focus:outline-none focus:ring-1 focus:ring-ring" /> </div> <div class="sm:col-span-2"> <label class="text-sm font-semibold text-gray-700 mb-2 block">Dirección de facturación</label> <input type="text" id="fac-direccion" placeholder="Calle 123, Oficina 456" class="w-full h-14 text-base rounded-md border bg-white px-3 py-2 shadow-sm focus:outline-none focus:ring-1 focus:ring-ring" /> </div> <!-- Comuna de facturación (select simple usando COMUNAS si ya existe) --> <div> <label class="text-sm font-semibold text-gray-700 mb-2 block">Comuna de facturación</label> <select id="fac-comuna" class="w-full h-14 text-base rounded-md border bg-white px-3 py-2 shadow-sm focus:outline-none focus:ring-1 focus:ring-ring"> <option value="">Selecciona una comuna</option> <!-- Se pobla por JS si existe COMUNAS --> </select> </div> </div> </div> <!-- Navegación Paso 4 --> <div class="flex justify-between pt-6"> <button type="button" id="step4BackBtn" class="inline-flex items-center justify-center gap-2 border shadow-sm h-9 px-8 py-6 text-lg rounded-lg"> Atrás </button> <button type="button" id="step4NextBtn" class="inline-flex items-center justify-center gap-2 shadow h-9 bg-[rgb(16,113,229)] hover:bg-blue-700 text-white px-8 py-6 text-lg rounded-lg"> Pagar </button> </div> </div> </div> </div> <section id="sidebarResumen" class="lg:col-span-1"> <div class="hidden lg:block"> <div class="sticky top-8"> <div class="rounded-xl border text-card-foreground p-6 shadow-xl bg-white relative"> <h3 class="text-2xl font-bold text-gray-900 mb-6">Resumen de Reserva</h3> <div class="space-y-6"> <!-- Plan / Precio --> <div> <div class="flex items-center justify-between mb-2"> <div class="flex items-center"> <svg class="lucide lucide-file-text w-5 h-5 text-[rgb(16,113,229)] mr-2" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"></path> <path d="M14 2v4a2 2 0 0 0 2 2h4"></path> <path d="M10 9H8"></path> <path d="M16 13H8"></path> <path d="M16 17H8"></path> </svg> <h4 class="font-semibold text-gray-700" id="resumen-plan">—</h4> </div> <span class="text-[rgb(16,113,229)] font-bold" id="resumen-precio">$0</span> </div> </div> <!-- Fecha y Hora --> <div> <div class="flex items-center mb-2"> <svg class="lucide lucide-calendar w-5 h-5 text-[rgb(16,113,229)] mr-2" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <path d="M8 2v4"></path><path d="M16 2v4"></path><rect width="18" height="18" x="3" y="4" rx="2"></rect><path d="M3 10h18"></path> </svg> <h4 class="font-semibold text-gray-700">Fecha y Hora</h4> </div> <p class="text-gray-900 ml-7" id="resumen-fecha">—</p> <p class="text-gray-900 ml-7" id="resumen-hora">—</p> </div> <div class="shrink-0 bg-border h-[1px] w-full"></div> <!-- Totales --> <div class="bg-blue-50 p-4 rounded-lg"> <div class="flex justify-between items-center mb-2"> <span class="text-gray-700">Informe:</span> <span class="font-semibold text-gray-900" id="resumen-servicio">$0</span> </div> <div class="flex justify-between items-center mb-3"> <span class="text-gray-700">Desplazamiento:</span> <span class="font-semibold text-gray-900" id="resumen-desplazamiento">$0</span> </div> <div class="shrink-0 bg-border h-[1px] w-full my-3"></div> <div class="flex justify-between items-center"> <span class="text-xl font-bold text-gray-900">Total a Pagar:</span> <span class="text-2xl font-bold text-[rgb(16,113,229)]" id="resumen-total">$0</span> </div> </div> <button id="btnPagar" class="hidden inline-flex items-center justify-center gap-2 whitespace-nowrap transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none shadow h-9 px-4 w-full bg-[rgb(16,113,229)] hover:bg-blue-700 text-white py-6 text-lg font-semibold rounded-lg disabled:opacity-50 disabled:cursor-not-allowed" disabled> Completa todos los pasos </button> </div> </div> </div> </div> </section> </div> <!-- Backdrop --> <div id="mobileSheetBackdrop" class="mobile-sheet-backdrop lg:hidden"></div> <!-- Bottom Sheet Resumen (móvil) --> <div id="mobileSheet" class="mobile-sheet lg:hidden" aria-hidden="true" role="dialog" aria-label="Resumen de reserva"> <div class="mobile-sheet__handle"></div> <div class="mobile-sheet__content"> <h3 class="text-xl font-bold text-gray-900 mb-4">Resumen de Reserva</h3> <div class="space-y-4 text-sm"> <div class="flex items-center gap-2"> <svg class="w-5 h-5 text-[rgb(16,113,229)]" viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/><path d="M10 9H8"/><path d="M16 13H8"/><path d="M16 17H8"/></svg> <div> <div class="text-gray-600">Informe</div> <div class="font-semibold text-gray-900" data-mobile-resumen-plan>—</div> </div> <div class="ml-auto font-bold text-[rgb(16,113,229)]" data-mobile-resumen-precio>—</div> </div> <div> <div class="text-gray-600 mb-1">Fecha y hora</div> <div class="font-medium text-gray-900" data-mobile-resumen-fecha>—</div> <div class="font-medium text-gray-900" data-mobile-resumen-hora>—</div> </div> <hr class="border-gray-200"> <div class="bg-blue-50 p-4 rounded-lg space-y-2"> <div class="flex justify-between"> <span class="text-gray-700">Informe:</span> <span class="font-semibold text-gray-900" data-mobile-resumen-servicio>—</span> </div> <div class="flex justify-between"> <span class="text-gray-700">Desplazamiento:</span> <span class="font-semibold text-gray-900" data-mobile-resumen-comuna>$0</span> </div> <hr class="border-gray-200 my-2"> <div class="flex justify-between items-center"> <span class="text-lg font-bold text-gray-900">Total a pagar:</span> <span class="text-2xl font-bold text-[rgb(16,113,229)]" data-mobile-resumen-total>—</span> </div> </div> <button class="w-full bg-[rgb(16,113,229)] text-white py-3 rounded-lg font-semibold mt-2" disabled data-mobile-pagar> Completa todos los pasos </button> </div> </div> </div> <div class="lg:hidden fixed bottom-0 left-0 right-0 z-50"> <button class="w-full bg-[rgb(16,113,229)] text-white py-4 font-semibold flex items-center justify-between px-6 shadow-lg" data-testid="toggle-summary-mobile-btn" aria-expanded="false" aria-controls="mobileSheet"> <span data-mobile-toggle-text>Ver Resumen</span> <span class="text-xl font-bold" id="mobileTotal"></span> </button> </div> </div> </div> </div> <section aria-atomic="false" aria-label="Notifications alt+T" aria-live="polite" aria-relevant="additions text" tabindex="-1"> </section> </div> <script> (function(){ // ==== Helpers ==== const fmtCLP = n => n.toLocaleString('es-CL', { style:'currency', currency:'CLP', maximumFractionDigits:0 }); const qs = new URLSearchParams(location.search); const planQS = qs.get('plan') || 'Inspección PreCompra'; const precioQS = Number(qs.get('precio') || 0); // ==== Estado ==== const DAYS_TOTAL = 11; // hoy + 10 const isSmall = () => matchMedia('(max-width: 640px)').matches; const windowSizeLg = 5; const windowSizeSm = 2; let visibleStart = 0; let selectedDateIndex = null; let selectedHour = null; const costoInforme = precioQS; window.costoDesplaz = typeof window.costoDesplaz === 'number' ? window.costoDesplaz : 0; // ==== Elements ==== const elCarousel = document.getElementById('dateCarousel'); const elPrev = document.getElementById('btnPrev'); const elNext = document.getElementById('btnNext'); const elHoursGrid = document.getElementById('hoursGrid'); const elPlan = document.getElementById('resumen-plan'); const elPrecio = document.getElementById('resumen-precio'); const elFecha = document.getElementById('resumen-fecha'); const elHora = document.getElementById('resumen-hora'); const elServ = document.getElementById('resumen-servicio'); const elDespl = document.getElementById('resumen-desplazamiento'); const elTotal = document.getElementById('resumen-total'); const elPay = document.getElementById('btnPagar'); const elMobileTotal = document.getElementById('mobileTotal'); // Inicializar resumen con plan/valor elPlan.textContent = planQS; elPrecio.textContent = fmtCLP(costoInforme); elServ.textContent = fmtCLP(costoInforme); elDespl.textContent = fmtCLP(window.costoDesplaz); elTotal.textContent = fmtCLP(costoInforme + window.costoDesplaz); if (elMobileTotal) elMobileTotal.textContent = fmtCLP(costoInforme + window.costoDesplaz); // ==== Generación de fechas ==== const dow = ['dom','lun','mar','mie','jue','vie','sáb']; const mon = ['ene','feb','mar','abr','may','jun','jul','ago','sep','oct','nov','dic']; const days = Array.from({length:DAYS_TOTAL}, (_,i) => { const d = new Date(); d.setDate(d.getDate() + i); return { iso: d.toISOString().slice(0,10), dow: dow[d.getDay()], day: d.getDate(), mon: mon[d.getMonth()], dateObj: d }; }); // ==== Horas por día (patrón simple) ==== function hoursForIndex(i){ const bases = [ ['09:00','10:00','11:00','12:00','13:00','14:00','15:00','16:00'], ['09:00','10:00','12:00','14:00','16:00'], ['10:00','11:00','13:00','15:00','17:00'], ['09:00','10:00','13:00','14:30','16:00'], ['10:00','12:00','14:00','15:00','17:00'] ]; return bases[i % bases.length]; } // ==== Render fechas ==== function renderDates(){ elCarousel.innerHTML = ''; const windowSize = isSmall() ? windowSizeSm : windowSizeLg; const end = Math.min(visibleStart + windowSize, days.length); for (let i=visibleStart; i<end; i++){ const d = days[i]; const btn = document.createElement('button'); const selected = (i===selectedDateIndex); btn.className = 'p-4 rounded-lg border-2 transition-all ' + (selected ? 'border-[rgb(16,113,229)] bg-blue-50' : 'border-gray-200 hover:border-blue-300'); btn.setAttribute('data-index', i); btn.innerHTML = ` <div class="text-center"> <div class="text-xs text-gray-600 capitalize">${d.dow}</div> <div class="text-2xl font-bold text-gray-700">${d.day}</div> <div class="text-xs font-bold text-gray-600 capitalize">${(d.mon || '').slice(0,3)}</div> </div> `; btn.addEventListener('click', () => { selectedDateIndex = i; selectedHour = null; renderDates(); renderHours(); updateSummary(); }); elCarousel.appendChild(btn); } elPrev.disabled = (visibleStart === 0); elNext.disabled = (end >= days.length); } // ==== Render horas ==== function renderHours(){ elHoursGrid.innerHTML = ''; if (selectedDateIndex === null){ elHoursGrid.innerHTML = '<div class="col-span-full text-gray-500"></div>'; return; } hoursForIndex(selectedDateIndex).forEach(h => { const b = document.createElement('button'); const selected = (h === selectedHour); b.className = 'py-3 px-4 rounded-lg border-2 font-medium transition-all ' + (selected ? 'border-[rgb(16,113,229)] bg-blue-50 text-[rgb(16,113,229)]' : 'border-gray-200 hover:border-blue-300 text-gray-700'); b.textContent = h; b.addEventListener('click', () => { selectedHour = h; renderHours(); updateSummary(); }); elHoursGrid.appendChild(b); }); } // ==== Resumen ==== function updateSummary(){ // Fecha if (selectedDateIndex != null){ const d = days[selectedDateIndex].dateObj; elFecha.textContent = new Intl.DateTimeFormat('es-CL', { dateStyle:'long' }).format(d); } else { elFecha.textContent = '—'; } // Hora elHora.textContent = selectedHour || '—'; // Totales const total = costoInforme + (window.costoDesplaz || 0); elServ.textContent = fmtCLP(costoInforme); elDespl.textContent = fmtCLP(window.costoDesplaz || 0); elTotal.textContent = fmtCLP(total); if (elMobileTotal) elMobileTotal.textContent = fmtCLP(total); elPay.disabled = !(selectedDateIndex !== null && selectedHour); if (window.syncMobileSummary) window.syncMobileSummary(); // Botón continuar elPay.disabled = !(selectedDateIndex !== null && selectedHour); if (window.syncMobileSummary) window.syncMobileSummary(); } window.updateResumen = updateSummary; window._updateSummary = updateSummary; // ==== Nav carrusel ==== elPrev.addEventListener('click', () => { const windowSize = isSmall() ? windowSizeSm : windowSizeLg; visibleStart = Math.max(0, visibleStart - windowSize); renderDates(); }); elNext.addEventListener('click', () => { const windowSize = isSmall() ? windowSizeSm : windowSizeLg; visibleStart = Math.min(days.length - windowSize, visibleStart + windowSize); renderDates(); }); window.addEventListener('resize', renderDates); // ==== Init ==== renderDates(); renderHours(); updateSummary(); })(); </script> <script> (function(){ // ... deja igual el código de open/close ... function syncMobileSummary(){ const q = s => document.querySelector(s); // Lee desde el resumen desktop por ID (coincide con tu HTML actual) const plan = q('#resumen-plan')?.textContent?.trim() || '—'; const precio = q('#resumen-precio')?.textContent?.trim() || '—'; const fecha = q('#resumen-fecha')?.textContent?.trim() || '—'; const hora = q('#resumen-hora')?.textContent?.trim() || '—'; const servicio = q('#resumen-servicio')?.textContent?.trim() || precio; const despl = q('#resumen-desplazamiento')?.textContent?.trim() || '$0'; const total = q('#resumen-total')?.textContent?.trim() || precio; const set = (sel, val) => { const el = q(sel); if (el) el.textContent = val; }; set('[data-mobile-resumen-plan]', plan); set('[data-mobile-resumen-precio]', precio); set('[data-mobile-resumen-fecha]', fecha); set('[data-mobile-resumen-hora]', hora); set('[data-mobile-resumen-servicio]', servicio); set('[data-mobile-resumen-comuna]', despl); // si luego es “Comuna”, cámbialo por el valor correcto set('[data-mobile-resumen-total]', total); // habilita o no el botón pagar del sheet const mobilePayBtn = q('[data-mobile-pagar]'); if (mobilePayBtn) mobilePayBtn.disabled = (fecha === '—' || hora === '—'); // actualiza el total del botón fijo inferior const mobileTotalInline = q('#mobileTotal'); if (mobileTotalInline && total !== '—') mobileTotalInline.textContent = total; } // Exponer para que lo llames cuando cambies fecha/hora/plan window.syncMobileSummary = syncMobileSummary; // Sincroniza al cargar syncMobileSummary(); // Mejora de robustez: evita error si no existe el span del texto const sheet = document.getElementById('mobileSheet'); const backdrop = document.getElementById('mobileSheetBackdrop'); const toggleBtn = document.querySelector('[data-testid="toggle-summary-mobile-btn"]'); const toggleText = document.querySelector('[data-mobile-toggle-text]'); // ==== Swipe down to close (mobile sheet) ==== let startY = 0; let currentY = 0; let dragging = false; const THRESHOLD = 70; // px para cerrar const sheetContent = sheet.querySelector('.mobile-sheet__content'); const sheetHandle = sheet.querySelector('.mobile-sheet__handle'); function onTouchStart(e){ if (!isOpen()) return; // Sólo inicia el gesto si: // - el toque viene del "handle", o // - el contenido está scrolleado arriba del todo (scrollTop === 0) const fromHandle = sheetHandle && (e.target === sheetHandle || sheetHandle.contains(e.target)); if (!fromHandle && sheetContent && sheetContent.scrollTop > 0) return; dragging = true; startY = (e.touches ? e.touches[0].clientY : e.clientY); currentY = startY; sheet.style.transition = 'none'; // desactiva anim para arrastre } function onTouchMove(e){ if (!dragging) return; const y = (e.touches ? e.touches[0].clientY : e.clientY); currentY = y; const delta = Math.max(0, currentY - startY); // sólo hacia abajo if (delta > 0){ e.preventDefault(); // evita scroll detrás sheet.style.transform = `translateY(${delta}px)`; // arrastra la hoja } } function onTouchEnd(){ if (!dragging) return; const delta = Math.max(0, currentY - startY); dragging = false; // restablece transición suave sheet.style.transition = 'transform .35s ease'; if (delta > THRESHOLD){ // cerrar si superó el umbral sheet.style.transform = ''; // limpia inline closeSheet(); } else { // volver a su posición original sheet.style.transform = 'translateY(0)'; // limpia el inline cuando termina la animación setTimeout(()=>{ sheet.style.transform = ''; }, 350); } } // Soporte táctil sheet.addEventListener('touchstart', onTouchStart, {passive:true}); sheet.addEventListener('touchmove', onTouchMove, {passive:false}); sheet.addEventListener('touchend', onTouchEnd, {passive:true}); // (Opcional) soporte mouse para pruebas en desktop sheet.addEventListener('mousedown', onTouchStart); window.addEventListener('mousemove', onTouchMove); window.addEventListener('mouseup', onTouchEnd); function openSheet(){ sheet.classList.add('open'); backdrop.classList.add('open'); sheet.setAttribute('aria-hidden','false'); if (toggleBtn) toggleBtn.setAttribute('aria-expanded','true'); document.documentElement.style.overflow = 'hidden'; document.body.style.overflow = 'hidden'; } function closeSheet(){ sheet.classList.remove('open'); backdrop.classList.remove('open'); sheet.setAttribute('aria-hidden','true'); if (toggleBtn) toggleBtn.setAttribute('aria-expanded','false'); document.documentElement.style.overflow = ''; document.body.style.overflow = ''; } function isOpen(){ return sheet.classList.contains('open'); } if (toggleBtn) { toggleBtn.addEventListener('click', ()=> { if(isOpen()){ closeSheet(); if (toggleText) toggleText.textContent = 'Ver Resumen'; } else { openSheet(); if (toggleText) toggleText.textContent = 'Ver Resumen'; } }); } if (backdrop) { backdrop.addEventListener('click', ()=> { closeSheet(); if (toggleText) toggleText.textContent = 'Ver Resumen'; }); } })(); </script> <script> (function(){ // Refs UI const step1Form = document.querySelector('[data-testid="step-1-form"]'); const step1Next = document.querySelector('[data-testid="step1-next-btn"]'); const step2Next = document.getElementById('step2NextBtn'); const step2Block = document.getElementById('step2'); const step3Block = document.getElementById('step3'); const step4Block = document.getElementById('step4'); const step1Icon = document.getElementById('step1Icon'); const step2Icon = document.getElementById('step2Icon'); const connector1 = document.getElementById('connector1'); const connector2 = document.getElementById('connector2'); const connector3 = document.getElementById('connector3'); const step2IconOpacity = document.getElementById('step2IconOpacity'); const step3Icon = document.getElementById('step3Icon'); const step3IconOpacity = document.getElementById('step3IconOpacity'); const step4Icon = document.getElementById('step4Icon'); const step4IconOpacity = document.getElementById('step4IconOpacity'); const step3Back = document.getElementById('step3BackBtn'); const step3Next = document.getElementById('step3NextBtn'); const step4Back = document.getElementById('step4BackBtn'); const step4Next = document.getElementById('step4NextBtn'); // Resumen (desktop) const elDespl = document.getElementById('resumen-desplazamiento'); const elTotal = document.getElementById('resumen-total'); // Acceso a variables/funciones del bloque de Paso 1: // - updateSummary usa costoInforme + costoDesplaz // - hacemos costoDesplaz global (window) para poder actualizarlo aquí. // Conviértelo en "let" en tu script principal: // let costoDesplaz = 0; // Y expón updateSummary si no lo está: // window._updateSummary = updateSummary; // Helpers visuales function setCompleted(circleEl){ if (!circleEl) return; circleEl.className = 'w-16 h-16 rounded-full flex items-center justify-center mb-2 bg-green-500 text-white'; circleEl.innerHTML = ` <svg xmlns="http://www.w3.org/2000/svg" class="lucide lucide-check w-8 h-8" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"> <path d="M20 6 9 17l-5-5"></path> </svg>`; } // --- MODIFICADAS: ahora restauran el SVG original --- function setActive(circleEl, wrapperEl){ if (!circleEl) return; circleEl.className = 'w-16 h-16 rounded-full flex items-center justify-center mb-2 transition-all bg-[rgb(16,113,229)] text-white ring-4 ring-blue-200'; restoreIcon(circleEl); // <-- asegura el ícono correcto (no el check) if (wrapperEl) wrapperEl.className = 'flex flex-col items-center cursor-pointer transition-all'; } function setInactive(circleEl, wrapperEl){ if (!circleEl) return; circleEl.className = 'w-16 h-16 rounded-full flex items-center justify-center mb-2 transition-all bg-gray-200 text-gray-500'; restoreIcon(circleEl); // <-- también restaura en estado inactivo if (wrapperEl) wrapperEl.className = 'flex flex-col items-center cursor-pointer transition-all opacity-40 cursor-not-allowed'; } function restoreIcon(circleEl) { if (!circleEl) return; if (circleEl === step1Icon) { circleEl.innerHTML = ` <svg aria-hidden="true" class="lucide lucide-file-text w-8 h-8" fill="none" height="24" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24" width="24"> <path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"></path> <path d="M14 2v4a2 2 0 0 0 2 2h4"></path> <path d="M10 9H8"></path> <path d="M16 13H8"></path> <path d="M16 17H8"></path> </svg>`; return; } if (circleEl === step2Icon) { circleEl.innerHTML = ` <svg aria-hidden="true" class="lucide lucide-map-pin w-8 h-8" fill="none" height="24" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24" width="24"> <path d="M20 10c0 4.993-5.539 10.193-7.399 11.799a1 1 0 0 1-1.202 0C9.539 20.193 4 14.993 4 10a8 8 0 0 1 16 0"></path> <circle cx="12" cy="10" r="3"></circle> </svg>`; return; } if (circleEl === step3Icon) { circleEl.innerHTML = ` <svg aria-hidden="true" class="lucide lucide-car w-8 h-8" fill="none" height="24" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24" width="24"> <path d="M19 17h2c.6 0 1-.4 1-1v-3c0-.9-.7-1.7-1.5-1.9C18.7 10.6 16 10 16 10s-1.3-1.4-2.2-2.3c-.5-.4-1.1-.7-1.8-.7H5c-.6 0-1.1.4-1.4.9l-1.4 2.9A3.7 3.7 0 0 0 2 12v4c0 .6.4 1 1 1h2"></path> <circle cx="7" cy="17" r="2"></circle> <path d="M9 17h6"></path> <circle cx="17" cy="17" r="2"></circle> </svg>`; return; } if (circleEl === step4Icon) { circleEl.innerHTML = ` <svg aria-hidden="true" class="lucide lucide-user w-8 h-8" fill="none" height="24" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24" width="24"> <path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"></path> <circle cx="12" cy="7" r="4"></circle> </svg>`; } } // Si no existe, creamos un wrapper a prueba de fallos const callUpdate = () => { if (window.syncMobileSummary) window.syncMobileSummary(); if (window._updateSummary) window._updateSummary(); }; // Ir a Paso 3 desde Paso 2 if (step2Next) { step2Next.addEventListener('click', () => { // Oculta Paso 2, muestra Paso 3 step2Block?.classList.add('hidden'); step3Block?.classList.remove('hidden'); // Paso 2 completado (verde con check) setCompleted(step2Icon); if (connector2) connector2.className = 'w-12 sm:w-24 h-1 rounded bg-green-500'; // Paso 3 activo (azul) setActive(step3Icon, step3IconOpacity); }); } // Paso 3 -> Paso 4 step3Next?.addEventListener('click', ()=>{ step3Block?.classList.add('hidden'); step4Block?.classList.remove('hidden'); setCompleted(step3Icon); // Paso 3 verde ✅ if (connector3) connector3.className = 'w-12 sm:w-24 h-1 rounded bg-green-500'; setActive(step4Icon, step4IconOpacity); // Paso 4 azul 🔵 }); // Paso 4 -> Paso 3 (Atrás) step4Back?.addEventListener('click', ()=>{ step4Block?.classList.add('hidden'); step3Block?.classList.remove('hidden'); setInactive(step4Icon, step4IconOpacity); // Paso 4 gris if (connector3) connector3.className = 'w-12 sm:w-24 h-1 rounded bg-gray-200'; setActive(step3Icon, step3IconOpacity); // Paso 3 azul }); // Toggle Boleta/Factura const rbBoleta = document.getElementById('doc-boleta'); const rbFactura = document.getElementById('doc-factura'); const facturaBox = document.getElementById('factura-block'); function toggleFactura(){ if (rbFactura?.checked) { facturaBox?.classList.remove('hidden'); } else { facturaBox?.classList.add('hidden'); } } rbBoleta?.addEventListener('change', toggleFactura); rbFactura?.addEventListener('change', toggleFactura); toggleFactura(); // init // Poblar comuna facturación si tienes COMUNAS en el scope global const facComuna = document.getElementById('fac-comuna'); if (facComuna && Array.isArray(window.COMUNAS)) { window.COMUNAS.forEach(c=>{ const opt = document.createElement('option'); opt.value = c.nombre; opt.textContent = c.nombre; facComuna.appendChild(opt); }); } // Volver de Paso 3 a Paso 2 if (step3Back) { step3Back.addEventListener('click', () => { // Oculta Paso 3, muestra Paso 2 step3Block?.classList.add('hidden'); step2Block?.classList.remove('hidden'); // Paso 3 vuelve a inactivo (gris) setInactive(step3Icon, step3IconOpacity); if (connector2) connector2.className = 'w-12 sm:w-24 h-1 rounded bg-gray-200'; // Paso 2 vuelve a activo (azul) setActive(step2Icon, step2IconOpacity); }); } // Avanzar a Paso 2 al click en "Siguiente" (si el botón está habilitado) if (step1Next) { step1Next.addEventListener('click', () => { if (step1Next.disabled) return; // 1) Mostrar Paso 2 / ocultar Paso 1 if (step1Form) step1Form.parentElement.classList.add('hidden'); // oculta la tarjeta del step 1 if (step2Block) step2Block.classList.remove('hidden'); // 2) Pintar Paso 1 como "completado" (verde con check) if (step1Icon) { step1Icon.className = 'w-16 h-16 rounded-full flex items-center justify-center mb-2 bg-green-500 text-white'; step1Icon.innerHTML = ` <svg xmlns="http://www.w3.org/2000/svg" class="lucide lucide-check w-8 h-8" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <path d="M20 6 9 17l-5-5"></path> </svg>`; } if (connector1) connector1.className = 'w-12 sm:w-24 h-1 rounded bg-green-500'; // 3) Activar Paso 2 en azul (como activo) if (step2Icon) { step2IconOpacity.className = 'flex flex-col items-center cursor-pointer transition-all'; step2Icon.className = 'w-16 h-16 rounded-full flex items-center justify-center mb-2 transition-all bg-[rgb(16,113,229)] text-white ring-4 ring-blue-200'; } callUpdate(); }); } // Botón "Atrás" de Paso 2 para volver al Paso 1 si quieres const step2Back = document.getElementById('step2BackBtn'); if (step2Back) { step2Back.addEventListener('click', () => { if (step2Block) step2Block.classList.add('hidden'); if (step1Form) step1Form.parentElement.classList.remove('hidden'); setInactive(step2Icon, step2IconOpacity); if (connector1) connector1.className = 'w-12 sm:w-24 h-1 rounded bg-gray-200'; // Paso 2 vuelve a activo (azul) setActive(step1Icon, null); // Paso 1 queda completado (si quieres revertirlo, aquí lo harías) callUpdate(); }); } // ==== Selección de comuna: actualiza desplazamiento y total ==== // Necesita que en tu script principal "costoDesplaz" sea let y esté en window. if (typeof window.costoDesplaz === 'undefined') { // fall-back si no está expuesto: leemos a través de dataset o dejamos var local window.costoDesplaz = 0; } const fmtCLP = n => n.toLocaleString('es-CL', { style:'currency', currency:'CLP', maximumFractionDigits:0 }); const comunasGrid = document.getElementById('comunasGrid'); if (comunasGrid) { comunasGrid.addEventListener('click', (e) => { const card = e.target.closest('[data-comuna][data-costo]'); if (!card) return; // Quitar selección previa comunasGrid.querySelectorAll('[data-comuna]').forEach(el=>{ el.classList.remove('border-[rgb(16,113,229)]','bg-blue-50'); el.classList.add('border-gray-200','bg-white'); }); // Marcar seleccionada card.classList.remove('border-gray-200','bg-white'); card.classList.add('border-[rgb(16,113,229)]','bg-blue-50'); // Actualizar desplazamiento const monto = Number(card.getAttribute('data-costo') || '0'); window.costoDesplaz = isNaN(monto) ? 0 : monto; // Refrescar resumen (desktop) if (elDespl) elDespl.textContent = fmtCLP(window.costoDesplaz); // Recalcular total: usamos la función existente en tu script principal if (typeof window._updateSummary === 'function') { window._updateSummary(); } else { // Pequeño fallback si no está expuesta: const elServ = document.getElementById('resumen-servicio'); const elTotal = document.getElementById('resumen-total'); const serv = elServ ? (elServ.textContent.replace(/\./g,'').replace(/[^\d]/g,'')|0) : 0; const total = serv + window.costoDesplaz; if (elTotal) elTotal.textContent = fmtCLP(total); } // Sincronizar móvil if (window.syncMobileSummary) window.syncMobileSummary(); }); } })(); </script> <!-- MODAL ÉXITO (oculto) --> <div id="success-modal" class="hidden fixed inset-0 z-[100000] flex items-center justify-center bg-black bg-opacity-50 p-4" data-testid="success-modal"> <div class="bg-white rounded-2xl shadow-2xl max-w-2xl w-full max-h-[90vh] overflow-y-auto"> <div class="p-8"> <button class="float-right p-2 hover:bg-gray-100 rounded-full transition-colors" data-testid="close-success-modal-btn" id="success-close-btn" aria-label="Cerrar"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-x w-6 h-6 text-gray-600"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg> </button> <div class="flex justify-center mb-6"> <div class="w-20 h-20 bg-green-100 rounded-full flex items-center justify-center"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-circle-check w-12 h-12 text-green-600" aria-hidden="true"> <circle cx="12" cy="12" r="10"></circle><path d="m9 12 2 2 4-4"></path></svg> </div> </div> <h2 class="text-3xl font-bold text-center text-gray-900 mb-2">¡Reserva Confirmada!</h2> <p class="text-center text-gray-600 mb-8">Tu reserva ha sido creada exitosamente</p> <div class="bg-blue-50 p-4 rounded-lg mb-6"> <p class="text-sm text-gray-600 text-center">ID de Reserva</p> <p id="success-id" class="text-lg font-mono font-semibold text-center text-[rgb(16,113,229)]">—</p> </div> <!-- Resumen principal --> <div class="space-y-4 mb-8"> <div class="grid grid-cols-2 gap-4"> <div><p class="text-sm text-gray-600">Servicio</p><p id="s-servicio" class="font-semibold text-gray-900">—</p></div> <div><p class="text-sm text-gray-600">Fecha</p><p id="s-fecha" class="font-semibold text-gray-900">—</p></div> <div><p class="text-sm text-gray-600">Hora</p><p id="s-hora" class="font-semibold text-gray-900">—</p></div> <div><p class="text-sm text-gray-600">Comuna</p><p id="s-comuna" class="font-semibold text-gray-900">—</p></div> <div><p class="text-sm text-gray-600">Vehículo</p><p id="s-vehiculo" class="font-semibold text-gray-900">—</p></div> <div><p class="text-sm text-gray-600">Total Pagado</p><p id="s-total" class="font-semibold text-[rgb(16,113,229)] text-lg">—</p></div> </div> </div> <!-- Contacto --> <div class="bg-gray-50 p-4 rounded-lg mb-6"> <p class="text-sm text-gray-600 mb-2">Información de contacto</p> <p id="s-nombre" class="text-gray-900">—</p> <p id="s-email" class="text-gray-600 text-sm">—</p> <p id="s-telefono" class="text-gray-600 text-sm">—</p> </div> <!-- Facturación (solo si factura) --> <div id="s-factura-block" class="hidden bg-gray-50 p-4 rounded-lg mb-6"> <p class="text-sm text-gray-600 mb-2">Datos de facturación</p> <div class="grid grid-cols-1 sm:grid-cols-2 gap-3"> <div><p class="text-xs text-gray-500">RUT</p><p id="s-fac-rut" class="font-medium text-gray-800">—</p></div> <div><p class="text-xs text-gray-500">Razón social</p><p id="s-fac-razon" class="font-medium text-gray-800">—</p></div> <div><p class="text-xs text-gray-500">Giro</p><p id="s-fac-giro" class="font-medium text-gray-800">—</p></div> <div><p class="text-xs text-gray-500">Email facturación</p><p id="s-fac-email" class="font-medium text-gray-800">—</p></div> <div class="sm:col-span-2"><p class="text-xs text-gray-500">Dirección</p><p id="s-fac-direccion" class="font-medium text-gray-800">—</p></div> <div><p class="text-xs text-gray-500">Comuna</p><p id="s-fac-comuna" class="font-medium text-gray-800">—</p></div> </div> </div> <div id="s-confirm-msg" class="bg-blue-50 p-4 rounded-lg mb-6 hidden"> <p class="text-sm text-gray-700 text-center"> Hemos enviado un correo de confirmación a <span id="s-confirm-email" class="font-semibold">—</span> con todos los detalles de tu reserva. </p> </div> <button id="success-new-btn" class="inline-flex items-center justify-center gap-2 whitespace-nowrap transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none shadow h-9 px-4 w-full bg-[rgb(16,113,229)] hover:bg-blue-700 text-white py-6 text-lg font-semibold rounded-lg" data-testid="new-booking-btn"> Hacer Nueva Reserva </button> </div> </div> </div> <script> (function(){ const $ = s => document.querySelector(s); const txt = (el, v) => { if (el) el.textContent = v || '—'; }; // UUID simple function uuidv4(){ if (window.crypto?.randomUUID) return crypto.randomUUID(); const x = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c=>{ const r = Math.random()*16|0, v = c === 'x' ? r : (r&0x3|0x8); return v.toString(16); }); return x; } // Lee valores del resumen ya pintado function readResumen(){ return { plan: $('#resumen-plan')?.textContent?.trim() || '—', fecha: $('#resumen-fecha')?.textContent?.trim() || '—', hora: $('#resumen-hora')?.textContent?.trim() || '—', total: $('#resumen-total')?.textContent?.trim() || '—' }; } // Comuna seleccionada: la guardamos cuando eliges en el selector // (si aún no lo hiciste, dentro de selectValue(nombre,costo) agrega: window.selectedComunaName = nombre;) function readComuna(){ return window.selectedComunaName || $('#comuna-trigger-label')?.textContent?.trim()?.replace(/\s*\+\$?.*$/, '') // intenta quitar "+$" || '—'; } function readVehiculo(){ const patente = $('#veh-patente')?.value?.trim(); const marca = $('#veh-marca')?.value?.trim(); const modelo = $('#veh-modelo')?.value?.trim(); const anio = $('#veh-anio')?.value?.trim(); const color = $('#veh-color')?.value?.trim(); // Prioriza patente; si no, compone Marca/Modelo/Año/Color const comp = [marca, modelo, anio, color].filter(Boolean).join(' '); return patente || comp || '—'; } function readCliente(){ return { nombre: $('#cli-nombre')?.value?.trim() || '—', email: $('#cli-email')?.value?.trim() || '—', telefono: $('#cli-telefono')?.value?.trim() || '—', tipoDoc: ($('#doc-factura')?.checked ? 'factura' : 'boleta') }; } function readFactura(){ if (!$('#doc-factura')?.checked) return null; return { rut: $('#fac-rut')?.value?.trim() || '—', razon: $('#fac-razon')?.value?.trim() || '—', giro: $('#fac-giro')?.value?.trim() || '—', email: $('#fac-email')?.value?.trim() || '—', direccion: $('#fac-direccion')?.value?.trim() || '—', comuna: $('#fac-comuna')?.value || '—' }; } function openSuccessModal(){ const res = readResumen(); const comuna = readComuna(); const veh = readVehiculo(); const cli = readCliente(); const fac = readFactura(); // ID de reserva txt($('#success-id'), uuidv4()); // Bloque principal txt($('#s-servicio'), res.plan); txt($('#s-fecha'), res.fecha); txt($('#s-hora'), res.hora); txt($('#s-comuna'), comuna); txt($('#s-vehiculo'), veh); txt($('#s-total'), res.total); // Contacto txt($('#s-nombre'), cli.nombre); txt($('#s-email'), cli.email); txt($('#s-telefono'), cli.telefono); // Facturación condicional const facBlock = $('#s-factura-block'); if (fac) { facBlock?.classList.remove('hidden'); txt($('#s-fac-rut'), fac.rut); txt($('#s-fac-razon'), fac.razon); txt($('#s-fac-giro'), fac.giro); txt($('#s-fac-email'), fac.email); txt($('#s-fac-direccion'), fac.direccion); txt($('#s-fac-comuna'), fac.comuna); } else { facBlock?.classList.add('hidden'); } // Mensaje de confirmación por correo (si hay email) const confirm = $('#s-confirm-msg'); const cEmail = $('#s-confirm-email'); if (cli.email && cli.email !== '—') { confirm?.classList.remove('hidden'); txt(cEmail, cli.email); } else { confirm?.classList.add('hidden'); } // Mostrar modal $('#success-modal')?.classList.remove('hidden'); document.documentElement.style.overflow = 'hidden'; document.body.style.overflow = 'hidden'; } function closeSuccessModal(){ $('#success-modal')?.classList.add('hidden'); document.documentElement.style.overflow = ''; document.body.style.overflow = ''; } // Wire botones document.getElementById('success-close-btn')?.addEventListener('click', closeSuccessModal); document.getElementById('success-modal')?.addEventListener('click', (e)=>{ if (e.target?.id === 'success-modal') closeSuccessModal(); // click en backdrop }); document.getElementById('success-new-btn')?.addEventListener('click', ()=>{ // Reset básico (puedes redirigir a paso 1 si prefieres) closeSuccessModal(); location.reload(); }); // Asegura que guardas la comuna elegida (si aún no lo hacías) // En tu selectValue(nombre,costo): agrega -> window.selectedComunaName = nombre; if (!window.selectedComunaName) { // intenta deducir de UI si ya estaba elegida const txtLabel = document.getElementById('comuna-trigger-label')?.textContent?.trim(); if (txtLabel && !/Selecciona/i.test(txtLabel)) window.selectedComunaName = txtLabel.replace(/\s*\+\$?.*$/, ''); } window.openSuccessModal = openSuccessModal; window.closeSuccessModal = closeSuccessModal; })(); </script> <!-- MODAL ERROR DE PAGO (oculto) --> <div id="error-modal" class="hidden fixed inset-0 z-[100000] flex items-center justify-center bg-black/50 p-4" data-testid="error-modal" aria-modal="true" role="dialog"> <div class="bg-white rounded-2xl shadow-2xl max-w-2xl w-full max-h-[90vh] overflow-y-auto"> <div class="p-8"> <button id="error-close-btn" class="float-right p-2 hover:bg-gray-100 rounded-full transition-colors" aria-label="Cerrar"> <svg xmlns="http://www.w3.org/2000/svg" class="lucide lucide-x w-6 h-6 text-gray-600" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg> </button> <div class="flex justify-center mb-6"> <div class="w-20 h-20 bg-red-100 rounded-full flex items-center justify-center"> <svg xmlns="http://www.w3.org/2000/svg" class="lucide lucide-octagon-x w-12 h-12 text-red-600" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <path d="m7.86 2h8.28L22 7.86v8.28L16.14 22H7.86L2 16.14V7.86Z"/><path d="m15 9-6 6"/><path d="m9 9 6 6"/> </svg> </div> </div> <h2 class="text-3xl font-bold text-center text-gray-900 mb-2">Pago no realizado</h2> <p class="text-center text-gray-600 mb-6" id="error-reason">No pudimos procesar tu pago. Intenta nuevamente.</p> <!-- (Opcional) Resumen breve para tranquilidad del usuario --> <div class="bg-blue-50 p-4 rounded-lg mb-6"> <div class="grid grid-cols-1 sm:grid-cols-3 gap-3 text-sm"> <div><p class="text-gray-600">Servicio</p><p id="e-servicio" class="font-semibold text-gray-900">—</p></div> <div><p class="text-gray-600">Fecha</p><p id="e-fecha" class="font-semibold text-gray-900">—</p></div> <div><p class="text-gray-600">Hora</p><p id="e-hora" class="font-semibold text-gray-900">—</p></div> <div><p class="text-gray-600">Comuna</p><p id="e-comuna" class="font-semibold text-gray-900">—</p></div> <div class="sm:col-span-2"><p class="text-gray-600">Total</p><p id="e-total" class="font-semibold text-[rgb(16,113,229)]">—</p></div> </div> </div> <div class="flex flex-col sm:flex-row gap-3"> <button id="error-retry-btn" class="w-full inline-flex items-center justify-center gap-2 shadow h-9 px-4 bg-[rgb(16,113,229)] hover:bg-blue-700 text-white py-6 text-lg font-semibold rounded-lg"> Reintentar pago </button> <button id="error-cancel-btn" class="w-full inline-flex items-center justify-center gap-2 border shadow-sm h-9 px-4 bg-white text-gray-800 py-6 text-lg font-semibold rounded-lg"> Cerrar </button> </div> </div> </div> </div> <script> (function(){ const $ = s => document.querySelector(s); const t = (el, v) => { if (el) el.textContent = v ?? '—'; }; function readResumen() { return { plan: $('#resumen-plan')?.textContent?.trim() || '—', fecha: $('#resumen-fecha')?.textContent?.trim() || '—', hora: $('#resumen-hora')?.textContent?.trim() || '—', total: $('#resumen-total')?.textContent?.trim() || '—' }; } function readComuna() { if (window.selectedComunaName) return window.selectedComunaName; const raw = $('#comuna-trigger-label')?.textContent?.trim() || ''; return raw.replace(/\s*\+\$?.*$/, '') || '—'; } function openPaymentErrorModal(reason) { const res = readResumen(); const comuna = readComuna(); t($('#error-reason'), reason || 'No pudimos procesar tu pago. Intenta nuevamente.'); t($('#e-servicio'), res.plan); t($('#e-fecha'), res.fecha); t($('#e-hora'), res.hora); t($('#e-comuna'), comuna); t($('#e-total'), res.total); $('#error-modal')?.classList.remove('hidden'); document.documentElement.style.overflow = 'hidden'; document.body.style.overflow = 'hidden'; } function closePaymentErrorModal() { $('#error-modal')?.classList.add('hidden'); document.documentElement.style.overflow = ''; document.body.style.overflow = ''; } // Exponer para que lo llames desde tu callback de pago window.openPaymentErrorModal = openPaymentErrorModal; window.closePaymentErrorModal = closePaymentErrorModal; // Botones $('#error-close-btn')?.addEventListener('click', closePaymentErrorModal); $('#error-cancel-btn')?.addEventListener('click', closePaymentErrorModal); $('#error-modal')?.addEventListener('click', (e)=>{ if (e.target?.id === 'error-modal') closePaymentErrorModal(); // click en backdrop }); // Reintentar: cierra y llama a un hook si existe $('#error-retry-btn')?.addEventListener('click', ()=>{ closePaymentErrorModal(); if (typeof window.retryPayment === 'function') { try { window.retryPayment(); } catch(e) {} } }); })(); </script> <script> (function(){ const btn = document.getElementById('step4NextBtn'); if (!btn) return; function setLoading(loading) { if (loading) { if (!btn.dataset._label) btn.dataset._label = btn.textContent.trim(); // guarda "Pagar" btn.disabled = true; btn.innerHTML = ` <span class="inline-flex items-center gap-2"> <svg class="animate-spin h-5 w-5" viewBox="0 0 24 24" fill="none"> <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle> <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z"></path> </svg> Procesando pago… </span> `; } else { btn.disabled = false; btn.textContent = btn.dataset._label || 'Pagar'; // ← vuelve a "Pagar" } } // MOCK de pago: devuelve una promesa que resuelve en éxito/fracaso aleatorio // Puedes forzar el resultado: // localStorage.setItem('mockPayOutcome', 'success') // o 'error' // // o con query string ?pay=success / ?pay=error function mockPay() { const forced = new URLSearchParams(location.search).get('pay') || localStorage.getItem('mockPayOutcome') || ''; const decide = () => { if (forced === 'success') return true; if (forced === 'error') return false; // 70% éxito por defecto return Math.random() < 0.7; }; return new Promise((resolve) => { // Simula latencia de red/pasarela const delay = 900 + Math.floor(Math.random() * 700); // 0.9s - 1.6s setTimeout(() => resolve(decide()), delay); }); } async function onFinalizeClick() { try { setLoading(true); const ok = await mockPay(); setLoading(false); if (ok) { // Marca paso 4 como completado (opcional, si ya tienes helpers) if (typeof window.setCompleted === 'function' && window.step4Icon) { window.setCompleted(window.step4Icon); } // Abre el modal de éxito if (typeof window.openSuccessModal === 'function') { window.openSuccessModal(); } } else { // Modal de error con motivo if (typeof window.openPaymentErrorModal === 'function') { window.openPaymentErrorModal('No pudimos procesar tu pago. Intenta nuevamente.'); } } } catch (e) { setLoading(false); if (typeof window.openPaymentErrorModal === 'function') { window.openPaymentErrorModal('Ocurrió un error inesperado. Por favor, reintenta.'); } } } btn.addEventListener('click', (e) => { e.preventDefault(); e.stopImmediatePropagation(); onFinalizeClick(); }, { capture: true }); })(); </script> </body> </html>
Save
cmd:
run