Cooreegido la exportacion de pdf cuando se edita una checklist ahora si se edita algo de la inspeccion hecha se actualiza el PDF

This commit is contained in:
2025-11-26 14:08:49 -03:00
parent 70f984bfdf
commit 4d3ac4bb5c
2 changed files with 283 additions and 26 deletions

View File

@@ -203,7 +203,7 @@ def send_completed_inspection_to_n8n(inspection, db):
# No lanzamos excepción para no interrumpir el flujo normal
BACKEND_VERSION = "1.0.25"
BACKEND_VERSION = "1.0.27"
app = FastAPI(title="Checklist Inteligente API", version=BACKEND_VERSION)
# S3/MinIO configuration
@@ -1043,31 +1043,11 @@ def update_inspection(
return db_inspection
@app.post("/api/inspections/{inspection_id}/complete", response_model=schemas.Inspection)
def complete_inspection(
inspection_id: int,
db: Session = Depends(get_db),
current_user: models.User = Depends(get_current_user)
):
inspection = db.query(models.Inspection).filter(
models.Inspection.id == inspection_id
).first()
if not inspection:
raise HTTPException(status_code=404, detail="Inspección no encontrada")
# Calcular score
answers = db.query(models.Answer).filter(models.Answer.inspection_id == inspection_id).all()
total_score = sum(a.points_earned for a in answers)
flagged_count = sum(1 for a in answers if a.is_flagged)
inspection.score = total_score
inspection.percentage = (total_score / inspection.max_score * 100) if inspection.max_score > 0 else 0
inspection.flagged_items_count = flagged_count
inspection.status = "completed"
inspection.completed_at = datetime.utcnow()
# Generar PDF profesional con diseño mejorado
def generate_inspection_pdf(inspection_id: int, db: Session) -> str:
"""
Genera el PDF de una inspección y lo sube a S3.
Retorna la URL del PDF generado.
"""
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.units import inch, mm
@@ -1077,6 +1057,10 @@ def complete_inspection(
from io import BytesIO
import requests
inspection = db.query(models.Inspection).filter(models.Inspection.id == inspection_id).first()
if not inspection:
raise HTTPException(status_code=404, detail="Inspección no encontrada")
buffer = BytesIO()
doc = SimpleDocTemplate(
buffer,
@@ -1354,7 +1338,9 @@ def complete_inspection(
print(f"❌ Error al generar PDF: {e}")
import traceback
traceback.print_exc()
raise HTTPException(status_code=500, detail=f"Error al generar PDF: {str(e)}")
# Subir a S3
buffer.seek(0)
now = datetime.now()
folder = f"{now.year}/{now.month:02d}"
@@ -1363,6 +1349,37 @@ def complete_inspection(
buffer.seek(0)
s3_client.upload_fileobj(buffer, S3_PDF_BUCKET, s3_key, ExtraArgs={"ContentType": "application/pdf"})
pdf_url = f"{S3_ENDPOINT}/{S3_PDF_BUCKET}/{s3_key}"
print(f"✅ PDF generado y subido a S3: {pdf_url}")
return pdf_url
@app.post("/api/inspections/{inspection_id}/complete", response_model=schemas.Inspection)
def complete_inspection(
inspection_id: int,
db: Session = Depends(get_db),
current_user: models.User = Depends(get_current_user)
):
inspection = db.query(models.Inspection).filter(
models.Inspection.id == inspection_id
).first()
if not inspection:
raise HTTPException(status_code=404, detail="Inspección no encontrada")
# Calcular score
answers = db.query(models.Answer).filter(models.Answer.inspection_id == inspection_id).all()
total_score = sum(a.points_earned for a in answers)
flagged_count = sum(1 for a in answers if a.is_flagged)
inspection.score = total_score
inspection.percentage = (total_score / inspection.max_score * 100) if inspection.max_score > 0 else 0
inspection.flagged_items_count = flagged_count
inspection.status = "completed"
inspection.completed_at = datetime.utcnow()
# Generar PDF usando función reutilizable
pdf_url = generate_inspection_pdf(inspection_id, db)
inspection.pdf_url = pdf_url
db.commit()
db.refresh(inspection)
@@ -1492,6 +1509,14 @@ def update_answer(
if not db_answer:
raise HTTPException(status_code=404, detail="Respuesta no encontrada")
# Obtener la inspección para verificar si está completada
inspection = db.query(models.Inspection).filter(
models.Inspection.id == db_answer.inspection_id
).first()
if not inspection:
raise HTTPException(status_code=404, detail="Inspección no encontrada")
# Recalcular puntos si cambió el status
if answer.status and answer.status != db_answer.status:
question = db.query(models.Question).filter(
@@ -1510,6 +1535,32 @@ def update_answer(
db.commit()
db.refresh(db_answer)
# Si la inspección está completada, regenerar PDF con los cambios
if inspection.status == "completed":
print(f"🔄 Regenerando PDF para inspección completada #{inspection.id}")
# Recalcular score de la inspección
answers = db.query(models.Answer).filter(
models.Answer.inspection_id == inspection.id
).all()
inspection.score = sum(a.points_earned for a in answers)
inspection.percentage = (inspection.score / inspection.max_score * 100) if inspection.max_score > 0 else 0
inspection.flagged_items_count = sum(1 for a in answers if a.is_flagged)
# Regenerar PDF
try:
pdf_url = generate_inspection_pdf(inspection.id, db)
inspection.pdf_url = pdf_url
db.commit()
print(f"✅ PDF regenerado exitosamente: {pdf_url}")
except Exception as e:
print(f"❌ Error regenerando PDF: {e}")
import traceback
traceback.print_exc()
# No lanzamos excepción para no interrumpir la actualización de la respuesta
return db_answer
@@ -1646,6 +1697,35 @@ def admin_edit_answer(
db.commit()
db.refresh(db_answer)
# Si la inspección está completada, regenerar PDF con los cambios
inspection = db.query(models.Inspection).filter(
models.Inspection.id == db_answer.inspection_id
).first()
if inspection and inspection.status == "completed":
print(f"🔄 Regenerando PDF para inspección completada #{inspection.id} (admin-edit)")
# Recalcular score de la inspección
answers = db.query(models.Answer).filter(
models.Answer.inspection_id == inspection.id
).all()
inspection.score = sum(a.points_earned for a in answers)
inspection.percentage = (inspection.score / inspection.max_score * 100) if inspection.max_score > 0 else 0
inspection.flagged_items_count = sum(1 for a in answers if a.is_flagged)
# Regenerar PDF
try:
pdf_url = generate_inspection_pdf(inspection.id, db)
inspection.pdf_url = pdf_url
db.commit()
print(f"✅ PDF regenerado exitosamente: {pdf_url}")
except Exception as e:
print(f"❌ Error regenerando PDF: {e}")
import traceback
traceback.print_exc()
# No lanzamos excepción para no interrumpir la actualización de la respuesta
return db_answer

177
docs/pdf-regeneration.md Normal file
View File

@@ -0,0 +1,177 @@
# Regeneración Automática de PDF al Editar Respuestas
## Descripción General
Se ha implementado la funcionalidad de regeneración automática del PDF de inspección cuando se editan respuestas en inspecciones completadas.
## Cambios Implementados
### 1. Nueva Función Reutilizable: `generate_inspection_pdf()`
**Ubicación**: `backend/app/main.py` (línea ~1046)
**Propósito**: Generar el PDF de una inspección y subirlo a S3.
**Parámetros**:
- `inspection_id: int` - ID de la inspección
- `db: Session` - Sesión de base de datos
**Retorna**: `str` - URL del PDF generado en S3
**Características**:
- Genera PDF profesional con diseño A4
- Incluye toda la información de la inspección
- Sube automáticamente a S3/MinIO
- Sobrescribe PDF existente si ya existe
- Maneja errores y excepciones
### 2. Actualización de `complete_inspection()`
**Ubicación**: `backend/app/main.py` (línea ~1358)
**Cambios**:
- Removido código duplicado de generación de PDF
- Ahora usa la función `generate_inspection_pdf()`
- Código más limpio y mantenible
**Antes**:
```python
# 300+ líneas de código de generación de PDF inline
```
**Después**:
```python
# Generar PDF usando función reutilizable
pdf_url = generate_inspection_pdf(inspection_id, db)
inspection.pdf_url = pdf_url
```
### 3. Actualización de `update_answer()`
**Ubicación**: `backend/app/main.py` (línea ~1497)
**Nuevas Funcionalidades**:
1. **Verificación de Estado**: Comprueba si la inspección está completada
2. **Recálculo de Puntuación**: Actualiza score, porcentaje y contadores
3. **Regeneración de PDF**: Genera nuevo PDF con los cambios
4. **Manejo de Errores**: No interrumpe la actualización si falla la generación del PDF
**Flujo de Trabajo**:
```python
1. Usuario edita respuesta
2. Backend actualiza Answer en BD
3. Backend verifica si inspection.status == "completed"
4. Si está completada:
a. Recalcula score total
b. Recalcula porcentaje
c. Recalcula items críticos
d. Genera nuevo PDF
e. Actualiza inspection.pdf_url
5. Retorna Answer actualizado
```
## Casos de Uso
### Caso 1: Editar Respuesta en Inspección en Progreso
```
- Usuario edita respuesta
- Respuesta se actualiza
- PDF NO se regenera (inspección no completada)
```
### Caso 2: Editar Respuesta en Inspección Completada
```
- Usuario edita respuesta
- Respuesta se actualiza
- Sistema detecta que inspección está completada
- Score se recalcula automáticamente
- PDF se regenera con los nuevos datos
- PDF anterior es sobrescrito en S3
```
## Ventajas de la Nueva Implementación
1. **DRY (Don't Repeat Yourself)**: Código de generación de PDF existe una sola vez
2. **Mantenibilidad**: Cambios al PDF solo se hacen en un lugar
3. **Automatización**: PDFs siempre reflejan el estado actual
4. **Consistencia**: Mismo diseño profesional en todas partes
5. **Robustez**: Manejo de errores sin interrumpir flujo principal
## Estructura del PDF Generado
El PDF incluye:
### Portada
- Título e ID de inspección
- Cuadro de información del vehículo (azul)
- Cuadro de información del cliente y mecánico (verde)
- Resumen de puntuación con colores según porcentaje
### Detalle de Inspección
- Agrupado por secciones
- Cada pregunta con:
- Icono de estado (✓ ok, ⚠ warning, ✕ critical)
- Respuesta y estado
- Comentarios
- Galería de imágenes (6 por fila)
### Footer
- Timestamp de generación
## Logs y Debugging
El sistema imprime logs útiles:
```python
# Al regenerar PDF
🔄 Regenerando PDF para inspección completada #123
# Al completar regeneración
PDF generado y subido a S3: https://...
# Si hay error
Error regenerando PDF: [detalle]
```
## Versión del Backend
**Versión actual**: `1.0.26`
Se incrementó la versión para reflejar esta nueva funcionalidad.
## Notas Técnicas
### S3/MinIO
- Los PDFs sobrescriben el archivo anterior con el mismo nombre
- Ruta: `{año}/{mes}/inspeccion_{id}_{placa}.pdf`
- Content-Type: `application/pdf`
### Base de Datos
- Campo `inspection.pdf_url` se actualiza automáticamente
- Score, porcentaje y flagged_items_count se recalculan
- Todo en una sola transacción
### Manejo de Errores
- Si falla la generación del PDF, se registra el error
- La actualización de la respuesta NO se revierte
- Se imprime traceback completo para debugging
## Próximos Pasos Sugeridos
1. ✅ Implementar regeneración de PDF (COMPLETADO)
2. ⏳ Ejecutar migraciones SQL para employee_code
3. ⏳ Probar flujo completo en ambiente de desarrollo
4. ⏳ Considerar notificación a n8n cuando se edita inspección completada
5. ⏳ Agregar campo `updated_at` a inspecciones para tracking de cambios
## Testing
Para probar la funcionalidad:
1. Completar una inspección
2. Verificar que se genera el PDF
3. Editar una respuesta (cambiar status, comentario, etc.)
4. Verificar en logs que se regenera el PDF
5. Descargar el PDF y confirmar que refleja los cambios
6. Verificar que el score se recalculó correctamente