From 00218a1a9290a880b3861614cf36c86327650d4c Mon Sep 17 00:00:00 2001 From: ronalds Date: Fri, 28 Nov 2025 14:54:28 -0300 Subject: [PATCH] Frontend v1.0.80: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit El botón "📄 Exportar PDF" ahora solo es visible para admin y asesor Los mecánicos (role === 'mechanic') pueden ver el modal de inspección pero NO pueden exportar el PDF Backend v1.0.82 (sin cambios adicionales) Resumen de permisos: ✅ Admin: Ver inspección + Exportar PDF + Ver historial + Inactivar ✅ Asesor: Ver inspección + Exportar PDF ❌ Mecánico: Solo ver inspección + Continuar incompletas --- backend/app/main.py | 57 +++++++++++++++++------- frontend/package.json | 2 +- frontend/src/App.jsx | 101 ++++++++++++++++++++++-------------------- 3 files changed, 93 insertions(+), 67 deletions(-) diff --git a/backend/app/main.py b/backend/app/main.py index 064fc31..1711db9 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -1428,15 +1428,14 @@ def generate_inspection_pdf(inspection_id: int, db: Session) -> str: # ===== PORTADA ===== elements.append(Spacer(1, 10*mm)) - # Función helper para cargar y dimensionar logos + # Función helper para cargar y dimensionar logos (optimizada) def load_logo(logo_url, max_width_mm=45, max_height_mm=35): """Carga un logo desde URL y retorna objeto Image con dimensiones ajustadas""" if not logo_url: return None try: - print(f"🔍 Cargando logo desde: {logo_url}") - logo_resp = requests.get(logo_url, timeout=10) - print(f"📡 Respuesta: {logo_resp.status_code}") + # Reducir timeout para respuestas más rápidas + logo_resp = requests.get(logo_url, timeout=5) if logo_resp.status_code == 200: logo_bytes = BytesIO(logo_resp.content) @@ -1455,21 +1454,42 @@ def generate_inspection_pdf(inspection_id: int, db: Session) -> str: logo_img.drawWidth = logo_width logo_img.drawHeight = logo_height - print(f"✅ Logo cargado ({logo_width/mm:.1f}mm x {logo_height/mm:.1f}mm)") return logo_img else: - print(f"❌ Error HTTP: {logo_resp.status_code}") + print(f"❌ Error HTTP cargando logo: {logo_resp.status_code}") except Exception as e: - print(f"⚠️ Error cargando logo: {e}") - import traceback - traceback.print_exc() + print(f"⚠️ Error cargando logo: {str(e)[:100]}") return None - # Cargar ambos logos - company_logo = load_logo(company_logo_url, max_width_mm=50, max_height_mm=35) - checklist_logo = load_logo(checklist_logo_url, max_width_mm=50, max_height_mm=35) + # Cargar ambos logos en paralelo usando ThreadPoolExecutor + from concurrent.futures import ThreadPoolExecutor, as_completed - # Crear tabla con logos en los extremos + company_logo = None + checklist_logo = None + + with ThreadPoolExecutor(max_workers=2) as executor: + futures = {} + if company_logo_url: + futures[executor.submit(load_logo, company_logo_url, 50, 35)] = 'company' + if checklist_logo_url: + futures[executor.submit(load_logo, checklist_logo_url, 50, 35)] = 'checklist' + + for future in as_completed(futures): + logo_type = futures[future] + try: + result = future.result() + if logo_type == 'company': + company_logo = result + if result: + print(f"✅ Logo empresa cargado") + elif logo_type == 'checklist': + checklist_logo = result + if result: + print(f"✅ Logo checklist cargado") + except Exception as e: + print(f"❌ Error procesando logo {logo_type}: {e}") + + # Crear tabla con logos en los extremos (ancho total disponible ~180mm) logo_row = [] # Logo empresa (izquierda) @@ -1478,7 +1498,7 @@ def generate_inspection_pdf(inspection_id: int, db: Session) -> str: else: logo_row.append(Paragraph("", styles['Normal'])) # Espacio vacío - # Espaciador central + # Espaciador central flexible logo_row.append(Paragraph("", styles['Normal'])) # Logo checklist (derecha) @@ -1487,13 +1507,16 @@ def generate_inspection_pdf(inspection_id: int, db: Session) -> str: else: logo_row.append(Paragraph("", styles['Normal'])) # Espacio vacío - # Crear tabla con logos - logo_table = Table([logo_row], colWidths=[60*mm, 60*mm, 60*mm]) + # Crear tabla con logos - columnas ajustadas para maximizar separación + # Columna 1: 55mm (logo empresa), Columna 2: 70mm (espacio), Columna 3: 55mm (logo checklist) + logo_table = Table([logo_row], colWidths=[55*mm, 70*mm, 55*mm]) logo_table.setStyle(TableStyle([ ('ALIGN', (0, 0), (0, 0), 'LEFT'), # Logo empresa a la izquierda ('ALIGN', (1, 0), (1, 0), 'CENTER'), # Centro vacío ('ALIGN', (2, 0), (2, 0), 'RIGHT'), # Logo checklist a la derecha - ('VALIGN', (0, 0), (-1, -1), 'TOP'), + ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'), # Alineación vertical al centro + # DEBUG: Agregar bordes para ver la distribución + # ('GRID', (0, 0), (-1, -1), 0.5, colors.red), ])) elements.append(logo_table) elements.append(Spacer(1, 5*mm)) diff --git a/frontend/package.json b/frontend/package.json index 268d66b..729bdd7 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,7 +1,7 @@ { "name": "checklist-frontend", "private": true, - "version": "1.0.79", + "version": "1.0.80", "type": "module", "scripts": { "dev": "vite", diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 4d17d63..0ca7a2c 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -3479,60 +3479,63 @@ function InspectionDetailModal({ inspection, user, onClose, onUpdate, onContinue {loadingAudit ? 'Cargando...' : 'Ver Historial de Cambios'} )} - + }} + className="px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition flex items-center gap-2" + > + 📄 + Exportar PDF + + )} {user?.role === 'admin' && ( <>