var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // var stdin_exports = {}; __export(stdin_exports, { default: () => stdin_default, featureConfig: () => featureConfig }); module.exports = __toCommonJS(stdin_exports); // virtual:core/dataSources.ts var dataSources = [ { key: "firmaSite", table: "facturacion__firma_site", access: ["public:read"], sensitive: ["clave", "valor"] } ]; // virtual:core/index.tsx var featureConfig = { dataSources, seedData: [], meta: { name: "Firma de contrato (p\xFAblico)", version: "1.0.0" } }; // virtual:page.tsx var import_react = __toESM(require("react")); var import_framer_motion = require("framer-motion"); var import_lucide_react = require("lucide-react"); var COMPANY_ID = 640; var AUTO_VERIFY = "contrato_sign_verify"; var AUTO_SUBMIT = "contrato_sign_submit"; var API_BASE = typeof window !== "undefined" && window.location.hostname === "localhost" ? "http://localhost:3000" : ""; var ACCENT = "#6B4EFF"; var EASE = [0.4, 0, 0.2, 1]; function readUrlParams() { if (typeof window === "undefined") return { token: "", previewToken: "" }; const search = new URLSearchParams(window.location.search); const token = (search.get("t") || search.get("token") || "").trim(); const previewToken = (search.get("preview") || "").trim(); try { if ((token || previewToken) && window.history && window.history.replaceState) { window.history.replaceState({}, document.title, window.location.pathname); } } catch (e) { } return { token, previewToken }; } function formatLongDate(d) { if (!d) return ""; try { return new Date(d).toLocaleDateString("es-ES", { day: "2-digit", month: "long", year: "numeric" }); } catch (e) { return ""; } } function downloadPdfFromBase64(base64, filename) { const byteChars = atob(base64); const byteNumbers = new Array(byteChars.length); for (let i = 0; i < byteChars.length; i++) byteNumbers[i] = byteChars.charCodeAt(i); const blob = new Blob([new Uint8Array(byteNumbers)], { type: "application/pdf" }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = filename; document.body.appendChild(a); a.click(); document.body.removeChild(a); setTimeout(() => URL.revokeObjectURL(url), 1e3); } async function callPublicAutomation(name, body) { const res = await fetch(`${API_BASE}/api/public/${COMPANY_ID}/automation/${name}`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) }); const json = await res.json().catch(() => ({})); if (!res.ok) { const err = new Error(json && (json.error || json.message) || "Request failed"); err.status = res.status; err.body = json; throw err; } if (json && json.success === false) { const err = new Error(json.error || json.message || "Automation failed"); err.status = res.status; err.body = json; throw err; } return json && typeof json === "object" && "result" in json ? json.result : json; } function mensajeDeMotivo(reason, status) { switch (reason) { case "too_many_attempts": return "Demasiados intentos desde esta conexi\xF3n. Espera unos minutos e int\xE9ntalo otra vez."; case "link_blocked": return "Este enlace se ha bloqueado por intentos fallidos. Pide uno nuevo a Gufi."; case "contract_not_ready": return "El contrato todav\xEDa no est\xE1 preparado para firmar. Avisa a Gufi."; case "documento_divergente": return "El documento ha cambiado desde que se emitieron los enlaces. No se puede firmar: pide a Gufi que reemita el contrato."; case "already_signed": return "Esta firma ya estaba registrada."; case "invalid_signature": return "No se ha podido leer el trazo de la firma. B\xF3rrala y vuelve a firmar."; case "pdf_failed": return "No se ha podido generar el PDF. Tu firma NO se ha registrado: vuelve a intentarlo."; case "unavailable": return "El servicio de firma no est\xE1 disponible ahora mismo. Int\xE9ntalo en unos minutos."; default: if (status === 429) return "Demasiados intentos. Espera unos minutos."; return "No hemos podido verificarte. Revisa el DNI/NIE e int\xE9ntalo de nuevo."; } } function FirmaContratoGufi({ id, gufi }) { const [params] = (0, import_react.useState)(() => readUrlParams()); const token = params.token; const previewToken = params.previewToken; const previewMode = !!previewToken; const [step, setStep] = (0, import_react.useState)("verify"); const [data, setData] = (0, import_react.useState)(null); const [dni, setDni] = (0, import_react.useState)(""); const [error, setError] = (0, import_react.useState)(null); const [loadingVerify, setLoadingVerify] = (0, import_react.useState)(false); const [loadingSign, setLoadingSign] = (0, import_react.useState)(false); const [pdfBase64, setPdfBase64] = (0, import_react.useState)(null); const [pdfFilename, setPdfFilename] = (0, import_react.useState)(null); const [emailSent, setEmailSent] = (0, import_react.useState)(false); const [accepted, setAccepted] = (0, import_react.useState)(false); const [hasSignature, setHasSignature] = (0, import_react.useState)(false); const [completo, setCompleto] = (0, import_react.useState)(null); const [pendientes, setPendientes] = (0, import_react.useState)([]); const canvasRef = (0, import_react.useRef)(null); const drawingRef = (0, import_react.useRef)(false); const lastPointRef = (0, import_react.useRef)(null); const hasSignatureRef = (0, import_react.useRef)(false); const initCanvas = (0, import_react.useCallback)(() => { const canvas = canvasRef.current; if (!canvas) return; const rect = canvas.getBoundingClientRect(); if (rect.width === 0) return; const ratio = Math.max(window.devicePixelRatio || 1, 1); canvas.width = rect.width * ratio; canvas.height = rect.height * ratio; const ctx = canvas.getContext("2d"); if (!ctx) return; ctx.scale(ratio, ratio); ctx.fillStyle = "#ffffff"; ctx.fillRect(0, 0, rect.width, rect.height); ctx.lineCap = "round"; ctx.lineJoin = "round"; ctx.strokeStyle = "#111827"; ctx.lineWidth = 2.4; hasSignatureRef.current = false; setHasSignature(false); }, []); const ajustarLienzo = (0, import_react.useCallback)(() => { if (!hasSignatureRef.current) initCanvas(); }, [initCanvas]); const pointerPos = (e) => { const canvas = canvasRef.current; const rect = canvas.getBoundingClientRect(); return { x: e.clientX - rect.left, y: e.clientY - rect.top }; }; const onPointerDown = (e) => { e.preventDefault(); drawingRef.current = true; lastPointRef.current = pointerPos(e.nativeEvent); canvasRef.current?.setPointerCapture(e.pointerId); }; const onPointerMove = (e) => { if (!drawingRef.current) return; const canvas = canvasRef.current; if (!canvas) return; const ctx = canvas.getContext("2d"); if (!ctx) return; const p = pointerPos(e.nativeEvent); const last = lastPointRef.current || p; ctx.beginPath(); ctx.moveTo(last.x, last.y); ctx.lineTo(p.x, p.y); ctx.stroke(); lastPointRef.current = p; if (!hasSignatureRef.current) { hasSignatureRef.current = true; setHasSignature(true); setError(null); } }; const onPointerUp = (e) => { drawingRef.current = false; lastPointRef.current = null; canvasRef.current?.releasePointerCapture(e.pointerId); }; const verificar = async (e) => { e?.preventDefault(); if (!dni.trim()) { setError("Introduce tu DNI/NIE"); return; } setLoadingVerify(true); setError(null); try { const result = await callPublicAutomation(AUTO_VERIFY, { token, dni: dni.trim() }); if (!result || !result.ok) { setError(mensajeDeMotivo(result && result.reason)); return; } setData(result); if (result.firmado) { setStep("success"); return; } setStep("sign"); } catch (err) { setError(mensajeDeMotivo(err?.body?.result?.reason, err?.status)); } finally { setLoadingVerify(false); } }; (0, import_react.useEffect)(() => { if (!previewMode) return; let cancelled = false; (async () => { setLoadingVerify(true); setError(null); try { const result = await callPublicAutomation(AUTO_VERIFY, { preview_token: previewToken }); if (cancelled) return; if (!result || !result.ok) { setError("No se pudo cargar la previsualizaci\xF3n. El enlace puede no ser v\xE1lido."); return; } setData(result); setStep("sign"); } catch (e) { if (!cancelled) setError("No se pudo cargar la previsualizaci\xF3n."); } finally { if (!cancelled) setLoadingVerify(false); } })(); return () => { cancelled = true; }; }, [previewMode, previewToken]); const firmar = async () => { if (!canvasRef.current || !hasSignatureRef.current) { setError("Dibuja tu firma antes de continuar."); return; } if (!accepted) { setError("Acepta las condiciones del contrato para continuar."); return; } setLoadingSign(true); setError(null); try { const signatureBase64 = canvasRef.current.toDataURL("image/png"); const result = await callPublicAutomation(AUTO_SUBMIT, { token, dni: dni.trim(), signature_base64: signatureBase64, user_agent: navigator.userAgent }); if (!result || !result.ok) { setError(mensajeDeMotivo(result && result.reason)); return; } setPdfBase64(result.pdf_base64 || null); setPdfFilename(result.pdf_filename || null); setEmailSent(Boolean(result.email_enviado)); setCompleto(typeof result.completo === "boolean" ? result.completo : null); setPendientes(Array.isArray(result.pendientes) ? result.pendientes : []); setStep("success"); } catch (err) { setError(mensajeDeMotivo(err?.body?.result?.reason, err?.status)); } finally { setLoadingSign(false); } }; if (!token && !previewMode) { return /* @__PURE__ */ import_react.default.createElement(ErrorScreen, { title: "Enlace incompleto", message: "Este enlace no lleva la credencial de firma. Pide a Gufi que te lo reenv\xEDe." }); } return /* @__PURE__ */ import_react.default.createElement(Layout, null, /* @__PURE__ */ import_react.default.createElement(import_framer_motion.AnimatePresence, { mode: "wait", initial: false }, step === "verify" && !previewMode && /* @__PURE__ */ import_react.default.createElement( import_framer_motion.motion.div, { key: "verify", initial: { opacity: 0, y: 12 }, animate: { opacity: 1, y: 0 }, exit: { opacity: 0, y: -8 }, transition: { duration: 0.35, ease: EASE }, className: "w-full" }, /* @__PURE__ */ import_react.default.createElement(VerifyScreen, { dni, setDni, error, setError, loading: loadingVerify, onSubmit: verificar }) ), step === "verify" && previewMode && /* @__PURE__ */ import_react.default.createElement(import_framer_motion.motion.div, { key: "preview-loading", initial: { opacity: 0 }, animate: { opacity: 1 }, className: "w-full" }, error ? /* @__PURE__ */ import_react.default.createElement(ErrorScreen, { title: "No se pudo previsualizar", message: error }) : /* @__PURE__ */ import_react.default.createElement("div", { className: "max-w-md mx-auto text-center py-24 text-zinc-500", "data-gufi-src": "page.tsx:296" }, /* @__PURE__ */ import_react.default.createElement(import_lucide_react.Loader2, { className: "w-7 h-7 animate-spin mx-auto mb-4", style: { color: ACCENT } }), "Cargando previsualizaci\xF3n del contrato\u2026")), step === "sign" && data && /* @__PURE__ */ import_react.default.createElement( import_framer_motion.motion.div, { key: "sign", initial: { opacity: 0, y: 16 }, animate: { opacity: 1, y: 0 }, exit: { opacity: 0, y: -12 }, transition: { duration: 0.4, ease: EASE }, className: "w-full" }, /* @__PURE__ */ import_react.default.createElement( SignScreen, { data, previewMode, canvasRef, onPointerDown, onPointerMove, onPointerUp, onClearSignature: initCanvas, onCanvasResize: ajustarLienzo, hasSignature, accepted, setAccepted, error, loading: loadingSign, onSubmit: firmar } ) ), step === "success" && /* @__PURE__ */ import_react.default.createElement( import_framer_motion.motion.div, { key: "success", initial: { opacity: 0, scale: 0.96 }, animate: { opacity: 1, scale: 1 }, exit: { opacity: 0, scale: 0.96 }, transition: { duration: 0.45, ease: EASE }, className: "w-full" }, /* @__PURE__ */ import_react.default.createElement( SuccessScreen, { data, emailSent, pdfBase64, pdfFilename, completo, pendientes } ) ))); } function VerifyScreen({ dni, setDni, error, setError, loading, onSubmit }) { return /* @__PURE__ */ import_react.default.createElement("div", { className: "bg-white rounded-3xl shadow-xl border border-zinc-100 p-8 max-w-md w-full mx-auto", "data-gufi-src": "page.tsx:337" }, /* @__PURE__ */ import_react.default.createElement(BrandMark, null), /* @__PURE__ */ import_react.default.createElement("div", { className: "text-center mb-8", "data-gufi-src": "page.tsx:339" }, /* @__PURE__ */ import_react.default.createElement("h1", { className: "text-2xl font-bold text-zinc-900 mb-2", "data-gufi-src": "page.tsx:340" }, "Verificaci\xF3n de identidad"), /* @__PURE__ */ import_react.default.createElement("p", { className: "text-zinc-500", "data-gufi-src": "page.tsx:341" }, "Introduce tu DNI/NIE para abrir el contrato y firmarlo.")), /* @__PURE__ */ import_react.default.createElement("form", { onSubmit, className: "space-y-4", "data-gufi-src": "page.tsx:344" }, /* @__PURE__ */ import_react.default.createElement("div", { "data-gufi-src": "page.tsx:345" }, /* @__PURE__ */ import_react.default.createElement("label", { className: "block text-sm font-medium text-zinc-700 mb-2", "data-gufi-src": "page.tsx:346" }, "DNI / NIE"), /* @__PURE__ */ import_react.default.createElement("div", { className: "relative", "data-gufi-src": "page.tsx:347" }, /* @__PURE__ */ import_react.default.createElement(import_lucide_react.IdCard, { className: "absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-zinc-400" }), /* @__PURE__ */ import_react.default.createElement( "input", { type: "text", value: dni, onChange: (e) => { setDni(e.target.value.toUpperCase()); setError(null); }, placeholder: "12345678X", className: "gf-input w-full pl-12 pr-4 py-4 rounded-xl border border-zinc-200 text-lg font-medium tracking-wide uppercase outline-none transition-all duration-200", autoFocus: true, autoComplete: "off", "data-gufi-src": "page.tsx:349" } )), error && /* @__PURE__ */ import_react.default.createElement( import_framer_motion.motion.p, { initial: { opacity: 0, y: -5 }, animate: { opacity: 1, y: 0 }, transition: { duration: 0.2, ease: EASE }, className: "text-sm text-rose-600 mt-2 flex items-start gap-1" }, /* @__PURE__ */ import_react.default.createElement(import_lucide_react.AlertCircle, { className: "w-4 h-4 mt-[2px] shrink-0" }), " ", /* @__PURE__ */ import_react.default.createElement("span", { "data-gufi-src": "page.tsx:360" }, error) )), /* @__PURE__ */ import_react.default.createElement( import_framer_motion.motion.button, { type: "submit", disabled: loading || !dni.trim(), whileHover: { scale: dni.trim() && !loading ? 1.01 : 1 }, whileTap: { scale: dni.trim() && !loading ? 0.99 : 1 }, transition: { duration: 0.15, ease: EASE }, className: "gf-grad w-full py-4 rounded-xl text-white font-semibold shadow-lg hover:shadow-xl disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2 transition-shadow" }, loading ? /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, /* @__PURE__ */ import_react.default.createElement(import_lucide_react.Loader2, { className: "w-5 h-5 animate-spin" }), " Verificando\u2026") : /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, /* @__PURE__ */ import_react.default.createElement(import_lucide_react.ShieldCheck, { className: "w-5 h-5" }), " Verificar y continuar") )), /* @__PURE__ */ import_react.default.createElement("p", { className: "text-center text-[11px] text-zinc-400 mt-6 leading-relaxed", "data-gufi-src": "page.tsx:375" }, "Tratamos tu DNI/NIE \xFAnicamente para verificar tu identidad como firmante autorizado (RGPD Art. 6.1.b \u2014 ejecuci\xF3n de contrato). No se comparte con terceros.")); } function SignScreen({ data, previewMode, canvasRef, onPointerDown, onPointerMove, onPointerUp, onClearSignature, onCanvasResize, hasSignature, accepted, setAccepted, error, loading, onSubmit }) { const canSubmit = accepted && hasSignature; const blocker = !accepted && !hasSignature ? "Firma y acepta las condiciones para continuar" : !accepted ? "Acepta las condiciones para continuar" : !hasSignature ? "Firma en el recuadro para continuar" : ""; const quien = data.firmante; return /* @__PURE__ */ import_react.default.createElement("div", { className: "max-w-3xl mx-auto", "data-gufi-src": "page.tsx:407" }, /* @__PURE__ */ import_react.default.createElement(BrandMark, { heading: `Contrato ${data.contrato.codigo}` }), /* @__PURE__ */ import_react.default.createElement( import_framer_motion.motion.div, { initial: { opacity: 0, y: 18 }, animate: { opacity: 1, y: 0 }, transition: { duration: 0.45, ease: EASE, delay: 0.05 }, className: "bg-white rounded-3xl shadow-2xl border border-zinc-100 overflow-hidden" }, previewMode && /* @__PURE__ */ import_react.default.createElement( "div", { className: "text-center text-[13px] font-semibold px-4 py-2.5", style: { background: "#FFF8E1", borderBottom: "1px solid #F3D98A", color: "#8A6D0F" }, "data-gufi-src": "page.tsx:413" }, "Previsualizaci\xF3n \xB7 este contrato todav\xEDa no est\xE1 firmado" ), !previewMode && quien && /* @__PURE__ */ import_react.default.createElement("div", { className: "px-6 sm:px-8 pt-5 pb-1", "data-gufi-src": "page.tsx:419" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "text-[12.5px] text-zinc-500", "data-gufi-src": "page.tsx:420" }, "Firmas como ", /* @__PURE__ */ import_react.default.createElement("strong", { className: "text-zinc-800", "data-gufi-src": "page.tsx:421" }, quien.nombre), quien.rol ? /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, " \xB7 ", quien.rol) : null, quien.parte_nombre ? /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, " \xB7 ", /* @__PURE__ */ import_react.default.createElement("span", { className: "text-zinc-700", "data-gufi-src": "page.tsx:423" }, quien.parte_nombre)) : null)), /* @__PURE__ */ import_react.default.createElement("div", { className: "gf-doc-scroll bg-zinc-50/60 px-4 sm:px-8 py-6", "data-gufi-src": "page.tsx:429" }, /* @__PURE__ */ import_react.default.createElement( "div", { className: "contract-html bg-white rounded-2xl shadow-sm border border-zinc-100 p-6 sm:p-9 mx-auto", style: { maxWidth: 720 }, dangerouslySetInnerHTML: { __html: data.contrato.documento_html }, "data-gufi-src": "page.tsx:430" } ), /* @__PURE__ */ import_react.default.createElement("div", { className: "mx-auto mt-4", style: { maxWidth: 720 }, "data-gufi-src": "page.tsx:433" }, previewMode ? /* @__PURE__ */ import_react.default.createElement("div", { style: { border: "1.5px dashed #CFC9FF", borderRadius: 12, padding: 22, textAlign: "center", color: "#6D63D6", fontSize: 11, fontWeight: 800, letterSpacing: 1.5, textTransform: "uppercase", background: "#FBFAFF" }, "data-gufi-src": "page.tsx:435" }, "Pendiente de firma") : /* @__PURE__ */ import_react.default.createElement("div", { className: "bg-white rounded-2xl border border-zinc-100 shadow-sm p-5", "data-gufi-src": "page.tsx:440" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "text-[11px] font-extrabold tracking-[0.12em] uppercase mb-3", style: { color: ACCENT }, "data-gufi-src": "page.tsx:441" }, "Firma aqu\xED", quien?.parte_nombre ? ` \xB7 por ${quien.parte_nombre}` : ""), /* @__PURE__ */ import_react.default.createElement( SignatureCanvas, { canvasRef, hasSignature, onPointerDown, onPointerMove, onPointerUp, onClear: onClearSignature, onResize: onCanvasResize } )))), Array.isArray(data.partes) && data.partes.length > 0 && /* @__PURE__ */ import_react.default.createElement("div", { className: "px-6 sm:px-8 pt-5", "data-gufi-src": "page.tsx:453" }, /* @__PURE__ */ import_react.default.createElement(EstadoPartes, { partes: data.partes })), !previewMode && /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, /* @__PURE__ */ import_react.default.createElement("div", { className: "px-6 sm:px-8 py-5 border-t border-zinc-100", "data-gufi-src": "page.tsx:460" }, /* @__PURE__ */ import_react.default.createElement( AppleCheckbox, { checked: accepted, onChange: setAccepted, label: "He le\xEDdo y acepto las condiciones del contrato, y firmo en representaci\xF3n de mi parte con poder bastante para ello." } )), /* @__PURE__ */ import_react.default.createElement("div", { className: "px-6 sm:px-8 py-5 border-t border-zinc-100 bg-gradient-to-b from-white to-zinc-50/60 flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3", "data-gufi-src": "page.tsx:465" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "text-xs text-zinc-500 sm:max-w-sm leading-relaxed", "data-gufi-src": "page.tsx:466" }, "Tu firma digital tiene la misma validez legal que una manuscrita (Reglamento eIDAS UE 910/2014, Ley 6/2020)."), /* @__PURE__ */ import_react.default.createElement("div", { className: "flex flex-col items-stretch sm:items-end gap-1.5", "data-gufi-src": "page.tsx:470" }, /* @__PURE__ */ import_react.default.createElement(SignButton, { onClick: onSubmit, disabled: !canSubmit, loading }), error && /* @__PURE__ */ import_react.default.createElement("p", { className: "text-xs text-rose-600 flex items-start gap-1 sm:justify-end", "data-gufi-src": "page.tsx:473" }, /* @__PURE__ */ import_react.default.createElement(import_lucide_react.AlertCircle, { className: "w-3.5 h-3.5 mt-[2px] shrink-0" }), " ", /* @__PURE__ */ import_react.default.createElement("span", { "data-gufi-src": "page.tsx:474" }, error)), !canSubmit && !loading && !error && /* @__PURE__ */ import_react.default.createElement("p", { className: "text-[11px] text-zinc-500 sm:text-right", "data-gufi-src": "page.tsx:477" }, blocker)))), previewMode && /* @__PURE__ */ import_react.default.createElement("div", { className: "px-6 sm:px-8 py-4 border-t border-zinc-100 text-center text-xs text-zinc-500", "data-gufi-src": "page.tsx:483" }, "Esto es una previsualizaci\xF3n. La firma se hace desde el enlace que recibe cada firmante.") ), data.contrato.documento_sha256 && /* @__PURE__ */ import_react.default.createElement("p", { className: "text-center text-[10.5px] text-zinc-400 mt-4", "data-gufi-src": "page.tsx:490" }, "Huella del documento: ", data.contrato.documento_sha256.slice(0, 24), "\u2026"), /* @__PURE__ */ import_react.default.createElement("p", { className: "text-center text-[11px] text-zinc-400 mt-2", "data-gufi-src": "page.tsx:494" }, "Powered by Gufi")); } function EstadoPartes({ partes }) { return /* @__PURE__ */ import_react.default.createElement("div", { className: "rounded-xl border border-zinc-200 overflow-hidden", "data-gufi-src": "page.tsx:501" }, partes.map((p, i) => /* @__PURE__ */ import_react.default.createElement("div", { key: p.parte + i, className: i > 0 ? "border-t border-zinc-200" : "", "data-gufi-src": "page.tsx:503" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "px-4 py-2 bg-zinc-50 text-[11px] font-extrabold tracking-[0.1em] uppercase text-zinc-500", "data-gufi-src": "page.tsx:504" }, p.parte_nombre || p.parte), p.firmantes.map((f, j) => /* @__PURE__ */ import_react.default.createElement("div", { key: j, className: "px-4 py-2.5 flex items-center justify-between gap-3 text-[13px]", "data-gufi-src": "page.tsx:508" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "min-w-0", "data-gufi-src": "page.tsx:509" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "font-medium text-zinc-800 truncate", "data-gufi-src": "page.tsx:510" }, f.nombre || "\u2014"), f.rol && /* @__PURE__ */ import_react.default.createElement("div", { className: "text-[11.5px] text-zinc-500 truncate", "data-gufi-src": "page.tsx:511" }, f.rol)), f.firmado ? /* @__PURE__ */ import_react.default.createElement("span", { className: "inline-flex items-center gap-1 text-[12px] font-semibold text-emerald-700 shrink-0", "data-gufi-src": "page.tsx:514" }, /* @__PURE__ */ import_react.default.createElement(import_lucide_react.CheckCircle2, { className: "w-4 h-4" }), " Firmado") : f.requerida === false ? /* @__PURE__ */ import_react.default.createElement("span", { className: "text-[12px] text-zinc-400 shrink-0", "data-gufi-src": "page.tsx:518" }, "No requerida") : /* @__PURE__ */ import_react.default.createElement("span", { className: "inline-flex items-center gap-1 text-[12px] font-semibold shrink-0", style: { color: "#B45309" }, "data-gufi-src": "page.tsx:520" }, /* @__PURE__ */ import_react.default.createElement(import_lucide_react.Clock, { className: "w-4 h-4" }), " Pendiente")))))); } function SignatureCanvas({ canvasRef, hasSignature, onPointerDown, onPointerMove, onPointerUp, onClear, onResize }) { (0, import_react.useEffect)(() => { const el = canvasRef.current; if (!el) return; const ro = new ResizeObserver(() => onResize()); ro.observe(el); return () => ro.disconnect(); }, [canvasRef, onResize]); return /* @__PURE__ */ import_react.default.createElement("div", { className: `relative w-full rounded-xl overflow-hidden border ${hasSignature ? "border-zinc-200" : "gf-sig-pulse border-zinc-200"}`, "data-gufi-src": "page.tsx:554" }, /* @__PURE__ */ import_react.default.createElement( "canvas", { ref: canvasRef, className: "w-full h-[130px] bg-white touch-none cursor-crosshair block", onPointerDown, onPointerMove, onPointerUp, onPointerCancel: onPointerUp, "data-gufi-src": "page.tsx:555" } ), /* @__PURE__ */ import_react.default.createElement(import_framer_motion.AnimatePresence, null, !hasSignature && /* @__PURE__ */ import_react.default.createElement( import_framer_motion.motion.div, { key: "hint", initial: { opacity: 0 }, animate: { opacity: 1 }, exit: { opacity: 0 }, transition: { duration: 0.25, ease: EASE }, className: "absolute inset-0 flex items-center justify-center pointer-events-none" }, /* @__PURE__ */ import_react.default.createElement("span", { className: "text-[11px] uppercase tracking-[0.18em] font-semibold select-none text-zinc-400", "data-gufi-src": "page.tsx:561" }, "Dibuja tu firma con el dedo o el rat\xF3n") )), /* @__PURE__ */ import_react.default.createElement(import_framer_motion.AnimatePresence, null, hasSignature && /* @__PURE__ */ import_react.default.createElement( import_framer_motion.motion.button, { type: "button", onClick: onClear, initial: { opacity: 0, scale: 0.8 }, animate: { opacity: 1, scale: 1 }, exit: { opacity: 0, scale: 0.8 }, transition: { duration: 0.2, ease: EASE }, className: "absolute top-2 right-2 p-1.5 rounded-lg bg-white/95 border border-zinc-200 hover:bg-zinc-50 transition-colors flex items-center gap-1 text-[11px] font-medium text-zinc-600", "aria-label": "Limpiar firma" }, /* @__PURE__ */ import_react.default.createElement(import_lucide_react.RefreshCw, { className: "w-3.5 h-3.5" }), " Repetir" ))); } function AppleCheckbox({ checked, onChange, label }) { return /* @__PURE__ */ import_react.default.createElement( "div", { role: "checkbox", "aria-checked": checked, tabIndex: 0, onClick: () => onChange(!checked), onKeyDown: (e) => { if (e.key === " " || e.key === "Enter") { e.preventDefault(); onChange(!checked); } }, className: "flex items-start gap-3 cursor-pointer select-none group focus:outline-none rounded-md py-1", "data-gufi-src": "page.tsx:584" }, /* @__PURE__ */ import_react.default.createElement( "div", { className: "relative flex-none w-6 h-6 rounded-md border-2 flex items-center justify-center transition-all duration-200 mt-[1px]", style: checked ? { background: ACCENT, borderColor: ACCENT } : { background: "#fff", borderColor: "#D4D4D8" }, "data-gufi-src": "page.tsx:588" }, /* @__PURE__ */ import_react.default.createElement(import_framer_motion.AnimatePresence, null, checked && /* @__PURE__ */ import_react.default.createElement( import_framer_motion.motion.span, { key: "check", initial: { scale: 0, opacity: 0 }, animate: { scale: 1, opacity: 1 }, exit: { scale: 0, opacity: 0 }, transition: { duration: 0.18, ease: EASE }, className: "flex items-center justify-center" }, /* @__PURE__ */ import_react.default.createElement(import_lucide_react.Check, { className: "w-4 h-4 text-white", strokeWidth: 3.5 }) )) ), /* @__PURE__ */ import_react.default.createElement("span", { className: `text-sm leading-relaxed transition-colors duration-200 ${checked ? "text-zinc-900" : "text-zinc-700"}`, "data-gufi-src": "page.tsx:599" }, label) ); } function SignButton({ onClick, disabled, loading }) { const isActive = !disabled && !loading; return /* @__PURE__ */ import_react.default.createElement( import_framer_motion.motion.button, { onClick, disabled: disabled || loading, whileHover: isActive ? { y: -1 } : void 0, whileTap: isActive ? { scale: 0.98 } : void 0, transition: { duration: 0.15, ease: EASE }, className: `relative px-7 py-3.5 rounded-xl font-semibold inline-flex items-center justify-center gap-2 overflow-hidden transition-all duration-300 ${isActive ? "text-white shadow-lg hover:shadow-xl" : "text-zinc-400 bg-zinc-200 cursor-not-allowed"}` }, /* @__PURE__ */ import_react.default.createElement( "span", { "aria-hidden": true, className: `absolute inset-0 transition-opacity duration-300 ${isActive ? "opacity-100" : "opacity-0"}`, style: { background: "linear-gradient(90deg, #6B4EFF 0%, #9A87FF 100%)" }, "data-gufi-src": "page.tsx:612" } ), /* @__PURE__ */ import_react.default.createElement("span", { className: "relative z-10 inline-flex items-center gap-2", "data-gufi-src": "page.tsx:614" }, loading ? /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, /* @__PURE__ */ import_react.default.createElement(import_lucide_react.Loader2, { className: "w-5 h-5 animate-spin" }), " Firmando\u2026") : /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, /* @__PURE__ */ import_react.default.createElement(import_lucide_react.CheckCircle2, { className: "w-5 h-5" }), " Firmar contrato")) ); } function SuccessScreen({ data, emailSent, pdfBase64, pdfFilename, completo, pendientes }) { const yaEstaba = data?.firmado && !pdfBase64; return /* @__PURE__ */ import_react.default.createElement("div", { className: "bg-white rounded-3xl shadow-xl border border-emerald-100 p-8 max-w-md w-full mx-auto text-center relative overflow-hidden", "data-gufi-src": "page.tsx:633" }, /* @__PURE__ */ import_react.default.createElement( import_framer_motion.motion.div, { initial: { scale: 0.5, rotate: -8, opacity: 0 }, animate: { scale: 1, rotate: 0, opacity: 1 }, transition: { duration: 0.55, ease: EASE, type: "spring", stiffness: 220, damping: 18 }, className: "w-20 h-20 rounded-full bg-emerald-100 flex items-center justify-center mx-auto mb-6 relative z-10" }, /* @__PURE__ */ import_react.default.createElement(import_lucide_react.CheckCircle2, { className: "w-10 h-10 text-emerald-600" }) ), /* @__PURE__ */ import_react.default.createElement( import_framer_motion.motion.h1, { initial: { opacity: 0, y: 8 }, animate: { opacity: 1, y: 0 }, transition: { duration: 0.35, ease: EASE, delay: 0.15 }, className: "text-2xl font-bold text-zinc-900 mb-2 relative z-10" }, yaEstaba ? "Ya hab\xEDas firmado" : completo === false ? "Firma registrada" : "\xA1Contrato firmado!" ), data?.contrato && /* @__PURE__ */ import_react.default.createElement( import_framer_motion.motion.p, { initial: { opacity: 0, y: 8 }, animate: { opacity: 1, y: 0 }, transition: { duration: 0.35, ease: EASE, delay: 0.22 }, className: "text-zinc-500 mb-4 relative z-10" }, "Contrato ", /* @__PURE__ */ import_react.default.createElement("strong", { "data-gufi-src": "page.tsx:646" }, data.contrato.codigo), data.firmado_at && /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, " \xB7 firmado el ", formatLongDate(data.firmado_at)) ), !yaEstaba && completo === false && /* @__PURE__ */ import_react.default.createElement( import_framer_motion.motion.div, { initial: { opacity: 0, y: 8 }, animate: { opacity: 1, y: 0 }, transition: { duration: 0.35, ease: EASE, delay: 0.26 }, className: "rounded-xl px-4 py-3 mb-4 text-[12.5px] leading-snug text-left relative z-10", style: { background: "#FFF8E1", color: "#8A6D0F", border: "1px solid #F3D98A" } }, "Tu firma ha quedado registrada. El contrato estar\xE1 completo cuando firmen las partes que faltan", pendientes.length ? /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, ": ", /* @__PURE__ */ import_react.default.createElement("strong", { "data-gufi-src": "page.tsx:655" }, pendientes.join(", "))) : null, ". Recibir\xE1s el PDF definitivo entonces." ), !yaEstaba && emailSent && /* @__PURE__ */ import_react.default.createElement( import_framer_motion.motion.p, { initial: { opacity: 0, y: 8 }, animate: { opacity: 1, y: 0 }, transition: { duration: 0.35, ease: EASE, delay: 0.3 }, className: "text-sm text-zinc-600 mb-6 leading-relaxed relative z-10" }, "Te hemos enviado por correo una copia con el PDF adjunto. Revisa tu bandeja de entrada (o spam)." ), !yaEstaba && !emailSent && /* @__PURE__ */ import_react.default.createElement( import_framer_motion.motion.p, { initial: { opacity: 0, y: 8 }, animate: { opacity: 1, y: 0 }, transition: { duration: 0.35, ease: EASE, delay: 0.3 }, className: "text-sm text-zinc-500 mb-6 relative z-10" }, "Descarga aqu\xED tu copia del contrato firmado." ), pdfBase64 ? /* @__PURE__ */ import_react.default.createElement( import_framer_motion.motion.button, { type: "button", onClick: () => downloadPdfFromBase64(pdfBase64, pdfFilename || "contrato.pdf"), initial: { opacity: 0, y: 8 }, animate: { opacity: 1, y: 0 }, transition: { duration: 0.35, ease: EASE, delay: 0.4 }, whileHover: { scale: 1.02 }, whileTap: { scale: 0.98 }, className: "gf-grad inline-flex items-center gap-2 px-6 py-3 rounded-xl text-white font-semibold shadow-lg hover:shadow-xl transition-shadow relative z-10" }, /* @__PURE__ */ import_react.default.createElement(import_lucide_react.Download, { className: "w-5 h-5" }), " Descargar PDF" ) : yaEstaba ? /* @__PURE__ */ import_react.default.createElement("p", { className: "text-xs text-zinc-400 relative z-10", "data-gufi-src": "page.tsx:678" }, "Pide a Gufi el PDF firmado si lo necesitas.") : null); } function BrandMark({ heading }) { return /* @__PURE__ */ import_react.default.createElement( import_framer_motion.motion.div, { initial: { opacity: 0, y: -12 }, animate: { opacity: 1, y: 0 }, transition: { duration: 0.4, ease: EASE }, className: "text-center mb-6" }, /* @__PURE__ */ import_react.default.createElement( "div", { role: "img", "aria-label": "Gufi", className: "mx-auto", style: { width: 88, height: 44, background: "url(https://gufi.dev/logo-gufi-purple.png) center / contain no-repeat" }, "data-gufi-src": "page.tsx:695" } ), /* @__PURE__ */ import_react.default.createElement("p", { className: "text-zinc-500 mt-3 text-sm", "data-gufi-src": "page.tsx:697" }, heading || "Firma de contrato") ); } function ErrorScreen({ title, message }) { return /* @__PURE__ */ import_react.default.createElement("div", { className: "min-h-[100dvh] bg-gradient-to-br from-rose-50 via-white to-orange-50 flex items-center justify-center p-4", "data-gufi-src": "page.tsx:704" }, /* @__PURE__ */ import_react.default.createElement( import_framer_motion.motion.div, { initial: { opacity: 0, y: 20 }, animate: { opacity: 1, y: 0 }, transition: { duration: 0.4, ease: EASE }, className: "bg-white rounded-3xl shadow-xl border border-rose-100 p-8 max-w-md w-full text-center" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "w-16 h-16 rounded-2xl bg-rose-100 flex items-center justify-center mx-auto mb-4", "data-gufi-src": "page.tsx:707" }, /* @__PURE__ */ import_react.default.createElement(import_lucide_react.AlertCircle, { className: "w-8 h-8 text-rose-600" })), /* @__PURE__ */ import_react.default.createElement("h1", { className: "text-xl font-bold text-zinc-900 mb-2", "data-gufi-src": "page.tsx:710" }, title), /* @__PURE__ */ import_react.default.createElement("p", { className: "text-zinc-500", "data-gufi-src": "page.tsx:711" }, message) )); } function Layout({ children }) { return /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, /* @__PURE__ */ import_react.default.createElement("style", { "data-gufi-src": "page.tsx:720" }, ` @keyframes gf-sig-pulse { 0%, 100% { box-shadow: 0 0 0 0 rgba(107, 78, 255, 0); } 50% { box-shadow: 0 0 0 4px rgba(107, 78, 255, 0.14); } } .gf-sig-pulse { animation: gf-sig-pulse 2.2s ease-in-out infinite; } .gf-grad { background: linear-gradient(90deg, #6B4EFF 0%, #9A87FF 100%); } .gf-input:focus { border-color: #6B4EFF; box-shadow: 0 0 0 3px rgba(107, 78, 255, 0.15); } .gf-page-bg { background: linear-gradient(135deg, #F6F5FF 0%, #FFFFFF 45%, #F3F1FF 100%); } .gf-doc-scroll { max-height: 66vh; overflow-y: auto; scroll-behavior: smooth; } /* Estilos del documento sellado (las clases que emite el panel) */ .contract-html { color: #0A2540; font-size: 13.5px; line-height: 1.6; } .contract-html .head { display: flex; justify-content: space-between; align-items: flex-start; gap: 16px; padding-bottom: 18px; border-bottom: 2px solid #E6E9FF; margin-bottom: 8px; } .contract-html .brand { font-size: 22px; font-weight: 800; letter-spacing: -0.02em; color: #0A2540; } .contract-html .brand span { color: #6B4EFF; } .contract-html .muted { color: #697386; font-size: 12.5px; line-height: 1.5; } .contract-html h1 { font-size: 18px; font-weight: 700; color: #0A2540; margin: 0; } .contract-html h2 { font-size: 15px; font-weight: 700; margin: 18px 0 6px; } .contract-html h3 { font-size: 12.5px; font-weight: 800; letter-spacing: 0.08em; text-transform: uppercase; color: #6B4EFF; margin: 22px 0 8px; } .contract-html p { margin: 10px 0; } .contract-html ul { list-style: disc; margin: 8px 0; padding-left: 22px; } .contract-html ol { list-style: decimal; margin: 8px 0; padding-left: 22px; } .contract-html li { margin: 3px 0; } .contract-html table { width: 100%; border-collapse: collapse; margin: 10px 0; font-size: 13px; } .contract-html th { text-align: left; background: #F5F3FF; color: #4C3FD8; font-weight: 700; padding: 9px 12px; border: 1px solid #E6E9FF; } .contract-html td { padding: 9px 12px; border: 1px solid #EEF1F6; vertical-align: top; } .contract-html strong { color: #0A2540; } `), /* @__PURE__ */ import_react.default.createElement("div", { className: "min-h-[100dvh] gf-page-bg py-6 sm:py-10 px-3 sm:px-4 flex items-start justify-center", "data-gufi-src": "page.tsx:749" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "w-full", "data-gufi-src": "page.tsx:750" }, children))); } // var stdin_default = FirmaContratoGufi;