From 32c7f79dd6177e3c90c403b3d9cc13d880852f58 Mon Sep 17 00:00:00 2001 From: gitea Date: Thu, 27 Nov 2025 17:06:09 -0300 Subject: [PATCH] =?UTF-8?q?Cambios=20implementados:=20=F0=9F=8E=A8=20Dise?= =?UTF-8?q?=C3=B1o=20Visual=20Mejorado:=20Portada=20m=C3=A1s=20espaciada?= =?UTF-8?q?=20-=20Espaciado=20superior=20aumentado=20para=20mejor=20presen?= =?UTF-8?q?taci=C3=B3n=20Cuadros=20con=20encabezados=20separados=20-=20Cad?= =?UTF-8?q?a=20secci=C3=B3n=20tiene=20un=20header=20coloreado=20profesiona?= =?UTF-8?q?l=20Bordes=20redondeados=20-=20Esquinas=20suavizadas=20para=20u?= =?UTF-8?q?n=20look=20m=C3=A1s=20moderno=20Separadores=20internos=20-=20L?= =?UTF-8?q?=C3=ADneas=20delgadas=20entre=20filas=20para=20mejor=20legibili?= =?UTF-8?q?dad=20Etiquetas=20diferenciadas=20-=20Labels=20en=20gris=20clar?= =?UTF-8?q?o,=20valores=20en=20negrita=20oscura=20=F0=9F=9A=97=20Cuadro=20?= =?UTF-8?q?de=20Veh=C3=ADculo:=20Header=20azul=20(#2563eb)=20con=20"?= =?UTF-8?q?=F0=9F=9A=97=20INFORMACI=C3=93N=20DEL=20VEH=C3=8DCULO"=20Conten?= =?UTF-8?q?ido=20blanco=20con=20bordes=20redondeados=20Layout=20limpio:=20?= =?UTF-8?q?etiqueta=20arriba,=20valor=20abajo=20por=20campo=20=F0=9F=93=84?= =?UTF-8?q?=20Cuadro=20de=20Inspecci=C3=B3n:=20Header=20verde=20(#16a34a)?= =?UTF-8?q?=20con=20"=F0=9F=93=84=20INFORMACI=C3=93N=20DE=20LA=20INSPECCI?= =?UTF-8?q?=C3=93N"=20Nombre=20de=20mec=C3=A1nico=20eliminado=20-=20Solo?= =?UTF-8?q?=20c=C3=B3digo=20de=20operario=20por=20privacidad=20Campos:=20N?= =?UTF-8?q?=C2=BA=20Pedido,=20OR=20N=C2=BA,=20C=C3=B3d.=20Operario,=20Fech?= =?UTF-8?q?a=20=F0=9F=93=8A=20Resumen=20de=20Evaluaci=C3=B3n:=20T=C3=ADtul?= =?UTF-8?q?o=20"=F0=9F=93=8A=20RESUMEN=20DE=20EVALUACI=C3=93N"=20centrado?= =?UTF-8?q?=20Grid=20de=204=20m=C3=A9tricas:=20Puntuaci=C3=B3n,=20Porcenta?= =?UTF-8?q?je,=20Estado,=20=C3=8Dtems=20Cr=C3=ADticos=20Borde=20din=C3=A1m?= =?UTF-8?q?ico=20seg=C3=BAn=20resultado=20(verde/amarillo/rojo)=20Estado?= =?UTF-8?q?=20textual:=20EXCELENTE/ACEPTABLE/DEFICIENTE=20Separadores=20in?= =?UTF-8?q?ternos=20para=20cada=20m=C3=A9trica=20Versi=C3=B3n=20Backend=20?= =?UTF-8?q?actualizada=20a=201.0.75?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/main.py | 176 +++++++++++++++++++++++++++++--------------- 1 file changed, 118 insertions(+), 58 deletions(-) diff --git a/backend/app/main.py b/backend/app/main.py index aa5e19a..d60b274 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -204,7 +204,7 @@ def send_completed_inspection_to_n8n(inspection, db): # No lanzamos excepción para no interrumpir el flujo normal -BACKEND_VERSION = "1.0.74" +BACKEND_VERSION = "1.0.75" app = FastAPI(title="Checklist Inteligente API", version=BACKEND_VERSION) # S3/MinIO configuration @@ -1374,58 +1374,98 @@ def generate_inspection_pdf(inspection_id: int, db: Session) -> str: checklist = db.query(models.Checklist).filter(models.Checklist.id == inspection.checklist_id).first() # ===== PORTADA ===== - elements.append(Spacer(1, 10*mm)) + elements.append(Spacer(1, 15*mm)) + + # Título con diseño moderno elements.append(Paragraph("📋 INFORME DE INSPECCIÓN VEHICULAR", title_style)) elements.append(Paragraph(f"N° {inspection.id}", subtitle_style)) - elements.append(Spacer(1, 5*mm)) + elements.append(Spacer(1, 10*mm)) - # Cuadro de información del vehículo - vehicle_data = [ - [Paragraph("🚗 INFORMACIÓN DEL VEHÍCULO", info_style)], - [Table([ - [Paragraph("Placa:", small_style), Paragraph(f"{inspection.vehicle_plate}", info_style)], - [Paragraph("Marca:", small_style), Paragraph(f"{inspection.vehicle_brand or 'N/A'}", info_style)], - [Paragraph("Modelo:", small_style), Paragraph(f"{inspection.vehicle_model or 'N/A'}", info_style)], - [Paragraph("Kilometraje:", small_style), Paragraph(f"{inspection.vehicle_km or 'N/A'} km", info_style)] - ], colWidths=[30*mm, 50*mm])] - ] + # Estilo para etiquetas de información + label_style = ParagraphStyle( + 'LabelStyle', + parent=styles['Normal'], + fontSize=9, + textColor=colors.HexColor('#64748b'), + spaceAfter=2 + ) - vehicle_table = Table(vehicle_data, colWidths=[85*mm]) - vehicle_table.setStyle(TableStyle([ - ('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#dbeafe')), - ('TEXTCOLOR', (0, 0), (-1, 0), colors.HexColor('#1e40af')), + value_style = ParagraphStyle( + 'ValueStyle', + parent=styles['Normal'], + fontSize=11, + textColor=colors.HexColor('#1e293b'), + fontName='Helvetica-Bold' + ) + + # Cuadro de información del vehículo con diseño moderno + vehicle_header = Table( + [[Paragraph("🚗 INFORMACIÓN DEL VEHÍCULO", ParagraphStyle('veh_header', parent=info_style, fontSize=12, textColor=colors.white))]], + colWidths=[85*mm] + ) + vehicle_header.setStyle(TableStyle([ + ('BACKGROUND', (0, 0), (-1, -1), colors.HexColor('#2563eb')), + ('PADDING', (0, 0), (-1, -1), 10), ('ALIGN', (0, 0), (-1, -1), 'LEFT'), - ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'), - ('FONTSIZE', (0, 0), (-1, 0), 11), - ('PADDING', (0, 0), (-1, -1), 8), - ('BACKGROUND', (0, 1), (-1, -1), colors.white), - ('BOX', (0, 0), (-1, -1), 1, colors.HexColor('#93c5fd')), - ('VALIGN', (0, 0), (-1, -1), 'TOP'), + ('ROUNDEDCORNERS', [6, 6, 0, 0]), ])) - # Cuadro de información del cliente e inspección - inspection_data = [ - [Paragraph("👤 INFORMACIÓN DEL CLIENTE", info_style)], - [Table([ - [Paragraph("Nº Pedido:", small_style), Paragraph(f"{inspection.order_number or 'N/A'}", info_style)], - [Paragraph("OR N°:", small_style), Paragraph(f"{inspection.or_number or 'N/A'}", info_style)], - [Paragraph("Mecánico:", small_style), Paragraph(f"{mechanic.full_name if mechanic else 'N/A'}", info_style)], - [Paragraph("Cód. Operario:", small_style), Paragraph(f"{inspection.mechanic_employee_code or 'N/A'}", info_style)], - [Paragraph("Fecha:", small_style), Paragraph(f"{inspection.started_at.strftime('%d/%m/%Y %H:%M') if inspection.started_at else 'N/A'}", info_style)] - ], colWidths=[30*mm, 50*mm])] - ] - - inspection_info_table = Table(inspection_data, colWidths=[85*mm]) - inspection_info_table.setStyle(TableStyle([ - ('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#dcfce7')), - ('TEXTCOLOR', (0, 0), (-1, 0), colors.HexColor('#15803d')), - ('ALIGN', (0, 0), (-1, -1), 'LEFT'), - ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'), - ('FONTSIZE', (0, 0), (-1, 0), 11), - ('PADDING', (0, 0), (-1, -1), 8), - ('BACKGROUND', (0, 1), (-1, -1), colors.white), - ('BOX', (0, 0), (-1, -1), 1, colors.HexColor('#86efac')), + vehicle_content = Table([ + [Paragraph("Placa", label_style), Paragraph(f"{inspection.vehicle_plate}", value_style)], + [Paragraph("Marca", label_style), Paragraph(f"{inspection.vehicle_brand or 'N/A'}", value_style)], + [Paragraph("Modelo", label_style), Paragraph(f"{inspection.vehicle_model or 'N/A'}", value_style)], + [Paragraph("Kilometraje", label_style), Paragraph(f"{inspection.vehicle_km or 'N/A'} km", value_style)] + ], colWidths=[25*mm, 60*mm]) + vehicle_content.setStyle(TableStyle([ + ('PADDING', (0, 0), (-1, -1), 10), + ('BACKGROUND', (0, 0), (-1, -1), colors.white), ('VALIGN', (0, 0), (-1, -1), 'TOP'), + ('LINEBELOW', (0, 0), (-1, -2), 0.5, colors.HexColor('#e2e8f0')), + ])) + + vehicle_table = Table( + [[vehicle_header], [vehicle_content]], + colWidths=[85*mm] + ) + vehicle_table.setStyle(TableStyle([ + ('BOX', (0, 0), (-1, -1), 1.5, colors.HexColor('#2563eb')), + ('VALIGN', (0, 0), (-1, -1), 'TOP'), + ('ROUNDEDCORNERS', [6, 6, 6, 6]), + ])) + + # Cuadro de información del cliente e inspección (sin nombre de mecánico por privacidad) + client_header = Table( + [[Paragraph("📄 INFORMACIÓN DE LA INSPECCIÓN", ParagraphStyle('client_header', parent=info_style, fontSize=12, textColor=colors.white))]], + colWidths=[85*mm] + ) + client_header.setStyle(TableStyle([ + ('BACKGROUND', (0, 0), (-1, -1), colors.HexColor('#16a34a')), + ('PADDING', (0, 0), (-1, -1), 10), + ('ALIGN', (0, 0), (-1, -1), 'LEFT'), + ('ROUNDEDCORNERS', [6, 6, 0, 0]), + ])) + + client_content = Table([ + [Paragraph("Nº Pedido", label_style), Paragraph(f"{inspection.order_number or 'N/A'}", value_style)], + [Paragraph("OR N°", label_style), Paragraph(f"{inspection.or_number or 'N/A'}", value_style)], + [Paragraph("Cód. Operario", label_style), Paragraph(f"{inspection.mechanic_employee_code or 'N/A'}", value_style)], + [Paragraph("Fecha", label_style), Paragraph(f"{inspection.started_at.strftime('%d/%m/%Y %H:%M') if inspection.started_at else 'N/A'}", value_style)] + ], colWidths=[25*mm, 60*mm]) + client_content.setStyle(TableStyle([ + ('PADDING', (0, 0), (-1, -1), 10), + ('BACKGROUND', (0, 0), (-1, -1), colors.white), + ('VALIGN', (0, 0), (-1, -1), 'TOP'), + ('LINEBELOW', (0, 0), (-1, -2), 0.5, colors.HexColor('#e2e8f0')), + ])) + + inspection_info_table = Table( + [[client_header], [client_content]], + colWidths=[85*mm] + ) + inspection_info_table.setStyle(TableStyle([ + ('BOX', (0, 0), (-1, -1), 1.5, colors.HexColor('#16a34a')), + ('VALIGN', (0, 0), (-1, -1), 'TOP'), + ('ROUNDEDCORNERS', [6, 6, 6, 6]), ])) # Tabla con ambos cuadros lado a lado @@ -1437,28 +1477,48 @@ def generate_inspection_pdf(inspection_id: int, db: Session) -> str: elements.append(info_table) elements.append(Spacer(1, 8*mm)) - # Resumen de puntuación + # Resumen de puntuación con diseño mejorado percentage = inspection.percentage score_color = colors.HexColor('#22c55e') if percentage >= 80 else colors.HexColor('#eab308') if percentage >= 60 else colors.HexColor('#ef4444') + score_label = "EXCELENTE" if percentage >= 80 else "ACEPTABLE" if percentage >= 60 else "DEFICIENTE" - score_data = [ + # Título de resumen + score_title = Table( + [[Paragraph("📊 RESUMEN DE EVALUACIÓN", ParagraphStyle('score_title', parent=info_style, fontSize=14, textColor=colors.HexColor('#1e293b'), alignment=TA_CENTER))]], + colWidths=[180*mm] + ) + score_title.setStyle(TableStyle([ + ('PADDING', (0, 0), (-1, -1), 8), + ('ALIGN', (0, 0), (-1, -1), 'CENTER'), + ])) + elements.append(score_title) + elements.append(Spacer(1, 3*mm)) + + # Cuadro de métricas con diseño moderno + metric_label = ParagraphStyle('metric_label', parent=small_style, fontSize=9, textColor=colors.HexColor('#64748b'), alignment=TA_CENTER) + metric_value = ParagraphStyle('metric_value', parent=info_style, fontSize=16, fontName='Helvetica-Bold', alignment=TA_CENTER) + + metrics_data = [ + [Paragraph("Puntuación", metric_label), Paragraph("Porcentaje", metric_label), Paragraph("Estado", metric_label), Paragraph("Ítems Críticos", metric_label)], [ - Paragraph("Puntuación Total", info_style), - Paragraph(f"{inspection.score} / {inspection.max_score}", info_style), - Paragraph(f"{percentage:.1f}%", info_style), - Paragraph(f"⚠️ {inspection.flagged_items_count} Críticos", info_style) + Paragraph(f"{inspection.score} / {inspection.max_score}", metric_value), + Paragraph(f"{percentage:.1f}%", metric_value), + Paragraph(f"{score_label}", ParagraphStyle('status_value', parent=metric_value, textColor=score_color)), + Paragraph(f"{inspection.flagged_items_count}", metric_value) ] ] - score_table = Table(score_data, colWidths=[45*mm, 45*mm, 45*mm, 45*mm]) + score_table = Table(metrics_data, colWidths=[45*mm, 45*mm, 45*mm, 45*mm]) score_table.setStyle(TableStyle([ - ('BACKGROUND', (0, 0), (-1, -1), score_color), - ('TEXTCOLOR', (0, 0), (-1, -1), colors.white), + ('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#f8fafc')), + ('BACKGROUND', (0, 1), (-1, -1), colors.white), ('ALIGN', (0, 0), (-1, -1), 'CENTER'), - ('FONTNAME', (0, 0), (-1, -1), 'Helvetica-Bold'), - ('FONTSIZE', (0, 0), (-1, -1), 12), - ('PADDING', (0, 0), (-1, -1), 10), - ('BOX', (0, 0), (-1, -1), 2, colors.white), + ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'), + ('PADDING', (0, 0), (-1, -1), 12), + ('BOX', (0, 0), (-1, -1), 2, score_color), + ('LINEABOVE', (0, 1), (-1, 1), 1.5, score_color), + ('GRID', (0, 0), (-1, -1), 0.5, colors.HexColor('#e2e8f0')), + ('ROUNDEDCORNERS', [8, 8, 8, 8]), ])) elements.append(score_table) elements.append(PageBreak())