Cambios implementados:

🎨 Diseño Visual Mejorado:
Portada más espaciada - Espaciado superior aumentado para mejor presentación
Cuadros con encabezados separados - Cada sección tiene un header coloreado profesional
Bordes redondeados - Esquinas suavizadas para un look más moderno
Separadores internos - Líneas delgadas entre filas para mejor legibilidad
Etiquetas diferenciadas - Labels en gris claro, valores en negrita oscura
🚗 Cuadro de Vehículo:
Header azul (#2563eb) con "🚗 INFORMACIÓN DEL VEHÍCULO"
Contenido blanco con bordes redondeados
Layout limpio: etiqueta arriba, valor abajo por campo
📄 Cuadro de Inspección:
Header verde (#16a34a) con "📄 INFORMACIÓN DE LA INSPECCIÓN"
Nombre de mecánico eliminado - Solo código de operario por privacidad
Campos: Nº Pedido, OR Nº, Cód. Operario, Fecha
📊 Resumen de Evaluación:
Título "📊 RESUMEN DE EVALUACIÓN" centrado
Grid de 4 métricas: Puntuación, Porcentaje, Estado, Ítems Críticos
Borde dinámico según resultado (verde/amarillo/rojo)
Estado textual: EXCELENTE/ACEPTABLE/DEFICIENTE
Separadores internos para cada métrica
Versión Backend actualizada a 1.0.75
This commit is contained in:
2025-11-27 17:06:09 -03:00
parent 1c9d7348ed
commit 32c7f79dd6

View File

@@ -204,7 +204,7 @@ def send_completed_inspection_to_n8n(inspection, db):
# No lanzamos excepción para no interrumpir el flujo normal # 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) app = FastAPI(title="Checklist Inteligente API", version=BACKEND_VERSION)
# S3/MinIO configuration # 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() checklist = db.query(models.Checklist).filter(models.Checklist.id == inspection.checklist_id).first()
# ===== PORTADA ===== # ===== 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("📋 INFORME DE INSPECCIÓN VEHICULAR", title_style))
elements.append(Paragraph(f"{inspection.id}", subtitle_style)) elements.append(Paragraph(f"{inspection.id}", subtitle_style))
elements.append(Spacer(1, 5*mm)) elements.append(Spacer(1, 10*mm))
# Cuadro de información del vehículo # Estilo para etiquetas de información
vehicle_data = [ label_style = ParagraphStyle(
[Paragraph("<b>🚗 INFORMACIÓN DEL VEHÍCULO</b>", info_style)], 'LabelStyle',
[Table([ parent=styles['Normal'],
[Paragraph("<b>Placa:</b>", small_style), Paragraph(f"{inspection.vehicle_plate}", info_style)], fontSize=9,
[Paragraph("<b>Marca:</b>", small_style), Paragraph(f"{inspection.vehicle_brand or 'N/A'}", info_style)], textColor=colors.HexColor('#64748b'),
[Paragraph("<b>Modelo:</b>", small_style), Paragraph(f"{inspection.vehicle_model or 'N/A'}", info_style)], spaceAfter=2
[Paragraph("<b>Kilometraje:</b>", small_style), Paragraph(f"{inspection.vehicle_km or 'N/A'} km", info_style)] )
], colWidths=[30*mm, 50*mm])]
]
vehicle_table = Table(vehicle_data, colWidths=[85*mm]) value_style = ParagraphStyle(
vehicle_table.setStyle(TableStyle([ 'ValueStyle',
('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#dbeafe')), parent=styles['Normal'],
('TEXTCOLOR', (0, 0), (-1, 0), colors.HexColor('#1e40af')), fontSize=11,
textColor=colors.HexColor('#1e293b'),
fontName='Helvetica-Bold'
)
# Cuadro de información del vehículo con diseño moderno
vehicle_header = Table(
[[Paragraph("<b>🚗 INFORMACIÓN DEL VEHÍCULO</b>", 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'), ('ALIGN', (0, 0), (-1, -1), 'LEFT'),
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'), ('ROUNDEDCORNERS', [6, 6, 0, 0]),
('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'),
])) ]))
# Cuadro de información del cliente e inspección vehicle_content = Table([
inspection_data = [ [Paragraph("Placa", label_style), Paragraph(f"{inspection.vehicle_plate}", value_style)],
[Paragraph("<b>👤 INFORMACIÓN DEL CLIENTE</b>", info_style)], [Paragraph("Marca", label_style), Paragraph(f"{inspection.vehicle_brand or 'N/A'}", value_style)],
[Table([ [Paragraph("Modelo", label_style), Paragraph(f"{inspection.vehicle_model or 'N/A'}", value_style)],
[Paragraph("<b>Nº Pedido:</b>", small_style), Paragraph(f"{inspection.order_number or 'N/A'}", info_style)], [Paragraph("Kilometraje", label_style), Paragraph(f"{inspection.vehicle_km or 'N/A'} km", value_style)]
[Paragraph("<b>OR N°:</b>", small_style), Paragraph(f"{inspection.or_number or 'N/A'}", info_style)], ], colWidths=[25*mm, 60*mm])
[Paragraph("<b>Mecánico:</b>", small_style), Paragraph(f"{mechanic.full_name if mechanic else 'N/A'}", info_style)], vehicle_content.setStyle(TableStyle([
[Paragraph("<b>Cód. Operario:</b>", small_style), Paragraph(f"{inspection.mechanic_employee_code or 'N/A'}", info_style)], ('PADDING', (0, 0), (-1, -1), 10),
[Paragraph("<b>Fecha:</b>", small_style), Paragraph(f"{inspection.started_at.strftime('%d/%m/%Y %H:%M') if inspection.started_at else 'N/A'}", info_style)] ('BACKGROUND', (0, 0), (-1, -1), colors.white),
], 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')),
('VALIGN', (0, 0), (-1, -1), 'TOP'), ('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("<b>📄 INFORMACIÓN DE LA INSPECCIÓN</b>", 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 # 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(info_table)
elements.append(Spacer(1, 8*mm)) elements.append(Spacer(1, 8*mm))
# Resumen de puntuación # Resumen de puntuación con diseño mejorado
percentage = inspection.percentage percentage = inspection.percentage
score_color = colors.HexColor('#22c55e') if percentage >= 80 else colors.HexColor('#eab308') if percentage >= 60 else colors.HexColor('#ef4444') 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("<b>Puntuación Total</b>", info_style), [[Paragraph("<b>📊 RESUMEN DE EVALUACIÓN</b>", ParagraphStyle('score_title', parent=info_style, fontSize=14, textColor=colors.HexColor('#1e293b'), alignment=TA_CENTER))]],
Paragraph(f"<b>{inspection.score} / {inspection.max_score}</b>", info_style), colWidths=[180*mm]
Paragraph(f"<b>{percentage:.1f}%</b>", info_style), )
Paragraph(f"<b>⚠️ {inspection.flagged_items_count} Críticos</b>", info_style) score_title.setStyle(TableStyle([
] ('PADDING', (0, 0), (-1, -1), 8),
]
score_table = Table(score_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),
('ALIGN', (0, 0), (-1, -1), 'CENTER'), ('ALIGN', (0, 0), (-1, -1), 'CENTER'),
('FONTNAME', (0, 0), (-1, -1), 'Helvetica-Bold'), ]))
('FONTSIZE', (0, 0), (-1, -1), 12), elements.append(score_title)
('PADDING', (0, 0), (-1, -1), 10), elements.append(Spacer(1, 3*mm))
('BOX', (0, 0), (-1, -1), 2, colors.white),
# 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(f"<b>{inspection.score}</b> / {inspection.max_score}", metric_value),
Paragraph(f"<b>{percentage:.1f}%</b>", metric_value),
Paragraph(f"<b>{score_label}</b>", ParagraphStyle('status_value', parent=metric_value, textColor=score_color)),
Paragraph(f"<b>{inspection.flagged_items_count}</b>", metric_value)
]
]
score_table = Table(metrics_data, colWidths=[45*mm, 45*mm, 45*mm, 45*mm])
score_table.setStyle(TableStyle([
('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#f8fafc')),
('BACKGROUND', (0, 1), (-1, -1), colors.white),
('ALIGN', (0, 0), (-1, -1), 'CENTER'),
('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(score_table)
elements.append(PageBreak()) elements.append(PageBreak())