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())