From 331dd40e764efc7c4d32e02b432c1856bc4497b7 Mon Sep 17 00:00:00 2001 From: Milton Gonzalez Date: Wed, 8 Apr 2026 23:45:47 -0500 Subject: [PATCH] Refine external signing flow and mobile PDF placement --- .../invitations/invitations.service.js | 23 +++-- .../src/components/ui/PdfSignaturePlacer.jsx | 89 ++++++++++++------- frontend/src/index.css | 5 ++ 3 files changed, 78 insertions(+), 39 deletions(-) diff --git a/backend/src/modules/invitations/invitations.service.js b/backend/src/modules/invitations/invitations.service.js index df862d6..34b0ed7 100644 --- a/backend/src/modules/invitations/invitations.service.js +++ b/backend/src/modules/invitations/invitations.service.js @@ -183,12 +183,6 @@ const addInviteesToFlow = async (documentId, { invitees }, ownerUserId, context throw error; } - if (flow.status === 'completed') { - const error = new Error('La cadena ya fue completada. Crea una nueva si necesitas más firmas.'); - error.statusCode = 409; - throw error; - } - const { document } = await ensureOwnerCanInvite(documentId, ownerUserId); const existingInvitees = await invitationsRepository.listInvitationsByFlow(flow.id); const currentCount = existingInvitees.length; @@ -198,11 +192,26 @@ const addInviteesToFlow = async (documentId, { invitees }, ownerUserId, context throw error; } - const existingEmails = new Set(existingInvitees.map((item) => String(item.invitee_email).toLowerCase())); const normalizedInvitees = invitees.map((invitee) => ({ name: invitee.name.trim(), email: invitee.email.trim().toLowerCase(), })); + const payloadEmails = new Set(); + + for (const invitee of normalizedInvitees) { + if (payloadEmails.has(invitee.email)) { + const error = new Error(`El correo ${invitee.email} está repetido en la solicitud.`); + error.statusCode = 409; + throw error; + } + payloadEmails.add(invitee.email); + } + + const existingEmails = new Set( + flow.status === 'completed' + ? [] + : existingInvitees.map((item) => String(item.invitee_email).toLowerCase()) + ); for (const invitee of normalizedInvitees) { if (existingEmails.has(invitee.email)) { diff --git a/frontend/src/components/ui/PdfSignaturePlacer.jsx b/frontend/src/components/ui/PdfSignaturePlacer.jsx index 33ac38a..5ce585e 100644 --- a/frontend/src/components/ui/PdfSignaturePlacer.jsx +++ b/frontend/src/components/ui/PdfSignaturePlacer.jsx @@ -1,4 +1,4 @@ -import { useState, useRef, useCallback } from 'react'; +import { useEffect, useRef, useState } from 'react'; import { Document, Page, pdfjs } from 'react-pdf'; import 'react-pdf/dist/Page/AnnotationLayer.css'; import 'react-pdf/dist/Page/TextLayer.css'; @@ -31,10 +31,33 @@ export default function PdfSignaturePlacer({ const [numPages, setNumPages] = useState(null); const [currentPage, setCurrentPage] = useState(1); const [sigBox, setSigBox] = useState(null); // { x, y } in container px + const stageRef = useRef(null); const containerRef = useRef(null); const [pageSize, setPageSize] = useState(null); // rendered px size + const [pageRenderWidth, setPageRenderWidth] = useState(600); const signatureWidth = getSignatureWidth(signatureStyle, signerName); + useEffect(() => { + const stage = stageRef.current; + if (!stage) return undefined; + + const updateWidth = () => { + const nextWidth = Math.max(280, Math.min(600, Math.floor(stage.clientWidth || 600))); + setPageRenderWidth(nextWidth); + }; + + updateWidth(); + + if (typeof ResizeObserver === 'undefined') { + window.addEventListener('resize', updateWidth); + return () => window.removeEventListener('resize', updateWidth); + } + + const observer = new ResizeObserver(() => updateWidth()); + observer.observe(stage); + return () => observer.disconnect(); + }, []); + const getCanvasMetrics = () => { const container = containerRef.current; const canvas = container?.querySelector('.react-pdf__Page__canvas'); @@ -59,12 +82,12 @@ export default function PdfSignaturePlacer({ setPageSize({ width: page.width, height: page.height }); }; - const handleContainerClick = (e) => { + const handlePointerDown = (event) => { const metrics = getCanvasMetrics(); if (!metrics) return; - const x = e.clientX - metrics.canvasRect.left; - const y = e.clientY - metrics.canvasRect.top; + const x = event.clientX - metrics.canvasRect.left; + const y = event.clientY - metrics.canvasRect.top; const clampedX = Math.min(Math.max(x, 0), metrics.width - signatureWidth); const clampedY = Math.min(Math.max(y, 0), metrics.height - SIG_H); @@ -177,35 +200,37 @@ export default function PdfSignaturePlacer({ -
- - setPageSize({ - width: p.width, height: p.height, - pdfWidth: p.originalWidth, pdfHeight: p.originalHeight, - })} - /> - +
+
+ + setPageSize({ + width: p.width, height: p.height, + pdfWidth: p.originalWidth, pdfHeight: p.originalHeight, + })} + /> + - {sigBox && ( -
e.stopPropagation()} - > - {signatureStyle === 'classic' &&
} - {renderPreview()} -
- )} + {sigBox && ( +
e.stopPropagation()} + > + {signatureStyle === 'classic' &&
} + {renderPreview()} +
+ )} +
{sigBox && ( diff --git a/frontend/src/index.css b/frontend/src/index.css index e41af8a..df73701 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -1425,6 +1425,10 @@ select { .pdf-placer-pages { display: flex; align-items: center; gap: 8px; } .pdf-placer-pages .btn { color: #fff; border-color: rgba(255,255,255,.2); padding: 4px 10px; } +.pdf-placer-stage { + width: 100%; +} + .pdf-placer-canvas { border: 2px dashed var(--primary); border-radius: 0 0 8px 8px; @@ -2323,6 +2327,7 @@ select { .pdf-placer-canvas { overflow: auto; + max-width: 100%; } .pdf-placer-canvas .react-pdf__Page,