Agregar envio a n8n usando .env para enviar cuando se guarda una inspeccion
This commit is contained in:
@@ -83,6 +83,124 @@ def send_answer_notification(answer, question, mechanic, db):
|
|||||||
print(f"❌ Error enviando notificación: {e}")
|
print(f"❌ Error enviando notificación: {e}")
|
||||||
# No lanzamos excepción para no interrumpir el flujo normal
|
# No lanzamos excepción para no interrumpir el flujo normal
|
||||||
|
|
||||||
|
|
||||||
|
def send_completed_inspection_to_n8n(inspection, db):
|
||||||
|
"""Envía la inspección completa con todas las respuestas e imágenes a n8n"""
|
||||||
|
try:
|
||||||
|
if not app_config.settings.NOTIFICACION_ENDPOINT:
|
||||||
|
print("No hay endpoint de notificación configurado")
|
||||||
|
return
|
||||||
|
|
||||||
|
print(f"\n🚀 Enviando inspección #{inspection.id} a n8n...")
|
||||||
|
|
||||||
|
# Obtener datos del mecánico
|
||||||
|
mechanic = db.query(models.User).filter(models.User.id == inspection.mechanic_id).first()
|
||||||
|
|
||||||
|
# Obtener checklist
|
||||||
|
checklist = db.query(models.Checklist).filter(models.Checklist.id == inspection.checklist_id).first()
|
||||||
|
|
||||||
|
# Obtener todas las respuestas con sus imágenes
|
||||||
|
answers = db.query(models.Answer).options(
|
||||||
|
joinedload(models.Answer.media_files),
|
||||||
|
joinedload(models.Answer.question)
|
||||||
|
).filter(models.Answer.inspection_id == inspection.id).all()
|
||||||
|
|
||||||
|
# Preparar respuestas con imágenes
|
||||||
|
respuestas_data = []
|
||||||
|
for answer in answers:
|
||||||
|
# Obtener URLs de imágenes
|
||||||
|
imagenes = []
|
||||||
|
for media in answer.media_files:
|
||||||
|
if media.file_type == "image":
|
||||||
|
imagenes.append({
|
||||||
|
"id": media.id,
|
||||||
|
"url": media.file_path,
|
||||||
|
"filename": media.filename
|
||||||
|
})
|
||||||
|
|
||||||
|
respuestas_data.append({
|
||||||
|
"id": answer.id,
|
||||||
|
"pregunta": {
|
||||||
|
"id": answer.question.id,
|
||||||
|
"texto": answer.question.text,
|
||||||
|
"seccion": answer.question.section,
|
||||||
|
"orden": answer.question.order
|
||||||
|
},
|
||||||
|
"respuesta": answer.answer_value,
|
||||||
|
"estado": answer.status,
|
||||||
|
"comentario": answer.comment,
|
||||||
|
"observaciones": answer.observations,
|
||||||
|
"puntos_obtenidos": answer.points_earned,
|
||||||
|
"es_critico": answer.is_flagged,
|
||||||
|
"imagenes": imagenes,
|
||||||
|
"ai_analysis": answer.ai_analysis
|
||||||
|
})
|
||||||
|
|
||||||
|
# Preparar datos completos de la inspección
|
||||||
|
inspeccion_data = {
|
||||||
|
"tipo": "inspeccion_completada",
|
||||||
|
"inspeccion": {
|
||||||
|
"id": inspection.id,
|
||||||
|
"estado": inspection.status,
|
||||||
|
"or_number": inspection.or_number,
|
||||||
|
"work_order_number": inspection.work_order_number,
|
||||||
|
"vehiculo": {
|
||||||
|
"placa": inspection.vehicle_plate,
|
||||||
|
"marca": inspection.vehicle_brand,
|
||||||
|
"modelo": inspection.vehicle_model,
|
||||||
|
"kilometraje": inspection.vehicle_km
|
||||||
|
},
|
||||||
|
"cliente": inspection.client_name,
|
||||||
|
"mecanico": {
|
||||||
|
"id": mechanic.id if mechanic else None,
|
||||||
|
"nombre": mechanic.full_name if mechanic else None,
|
||||||
|
"email": mechanic.email if mechanic else None,
|
||||||
|
"codigo_operario": inspection.mechanic_employee_code
|
||||||
|
},
|
||||||
|
"checklist": {
|
||||||
|
"id": checklist.id if checklist else None,
|
||||||
|
"nombre": checklist.name if checklist else None
|
||||||
|
},
|
||||||
|
"puntuacion": {
|
||||||
|
"obtenida": inspection.score,
|
||||||
|
"maxima": inspection.max_score,
|
||||||
|
"porcentaje": round(inspection.percentage, 2),
|
||||||
|
"items_criticos": inspection.flagged_items_count
|
||||||
|
},
|
||||||
|
"fechas": {
|
||||||
|
"inicio": inspection.started_at.isoformat() if inspection.started_at else None,
|
||||||
|
"completado": inspection.completed_at.isoformat() if inspection.completed_at else None
|
||||||
|
},
|
||||||
|
"pdf_url": inspection.pdf_url,
|
||||||
|
"firma": inspection.signature_data
|
||||||
|
},
|
||||||
|
"respuestas": respuestas_data,
|
||||||
|
"timestamp": datetime.utcnow().isoformat()
|
||||||
|
}
|
||||||
|
|
||||||
|
# Enviar al webhook de n8n
|
||||||
|
print(f"📤 Enviando {len(respuestas_data)} respuestas con imágenes a n8n...")
|
||||||
|
response = requests.post(
|
||||||
|
app_config.settings.NOTIFICACION_ENDPOINT,
|
||||||
|
json=inspeccion_data,
|
||||||
|
timeout=30 # Timeout más largo para inspecciones completas
|
||||||
|
)
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
print(f"✅ Inspección #{inspection.id} enviada exitosamente a n8n")
|
||||||
|
print(f" - {len(respuestas_data)} respuestas")
|
||||||
|
print(f" - {sum(len(r['imagenes']) for r in respuestas_data)} imágenes")
|
||||||
|
else:
|
||||||
|
print(f"⚠️ Error al enviar inspección a n8n: {response.status_code}")
|
||||||
|
print(f" Response: {response.text[:200]}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Error enviando inspección a n8n: {e}")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
# No lanzamos excepción para no interrumpir el flujo normal
|
||||||
|
|
||||||
|
|
||||||
BACKEND_VERSION = "1.0.25"
|
BACKEND_VERSION = "1.0.25"
|
||||||
app = FastAPI(title="Checklist Inteligente API", version=BACKEND_VERSION)
|
app = FastAPI(title="Checklist Inteligente API", version=BACKEND_VERSION)
|
||||||
|
|
||||||
@@ -1028,6 +1146,10 @@ def complete_inspection(
|
|||||||
inspection.pdf_url = pdf_url
|
inspection.pdf_url = pdf_url
|
||||||
db.commit()
|
db.commit()
|
||||||
db.refresh(inspection)
|
db.refresh(inspection)
|
||||||
|
|
||||||
|
# Enviar inspección completa a n8n con todas las respuestas e imágenes
|
||||||
|
send_completed_inspection_to_n8n(inspection, db)
|
||||||
|
|
||||||
return inspection
|
return inspection
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
196
docs/webhook-n8n.md
Normal file
196
docs/webhook-n8n.md
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
# Documentación de Webhook - n8n
|
||||||
|
|
||||||
|
## Endpoint
|
||||||
|
El endpoint configurado en `.env`:
|
||||||
|
```
|
||||||
|
NOTIFICACION_ENDPOINT=https://n8nw.comercialarmin.com.py/webhook/53284540-edc4-418f-b1bf-a70a805f8212
|
||||||
|
```
|
||||||
|
|
||||||
|
## Evento: Inspección Completada
|
||||||
|
|
||||||
|
### Cuándo se envía
|
||||||
|
Cuando se completa una inspección (endpoint: `POST /api/inspections/{id}/complete`)
|
||||||
|
|
||||||
|
### Estructura del JSON
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"tipo": "inspeccion_completada",
|
||||||
|
"inspeccion": {
|
||||||
|
"id": 123,
|
||||||
|
"estado": "completed",
|
||||||
|
"or_number": "OR-001",
|
||||||
|
"work_order_number": "WO-123",
|
||||||
|
"vehiculo": {
|
||||||
|
"placa": "ABC-123",
|
||||||
|
"marca": "Toyota",
|
||||||
|
"modelo": "Corolla 2020",
|
||||||
|
"kilometraje": 50000
|
||||||
|
},
|
||||||
|
"cliente": "Juan Pérez",
|
||||||
|
"mecanico": {
|
||||||
|
"id": 5,
|
||||||
|
"nombre": "Carlos Méndez",
|
||||||
|
"email": "carlos@example.com",
|
||||||
|
"codigo_operario": "OPR-001"
|
||||||
|
},
|
||||||
|
"checklist": {
|
||||||
|
"id": 1,
|
||||||
|
"nombre": "Inspección Vehicular Completa"
|
||||||
|
},
|
||||||
|
"puntuacion": {
|
||||||
|
"obtenida": 85,
|
||||||
|
"maxima": 100,
|
||||||
|
"porcentaje": 85.0,
|
||||||
|
"items_criticos": 2
|
||||||
|
},
|
||||||
|
"fechas": {
|
||||||
|
"inicio": "2025-11-26T10:30:00",
|
||||||
|
"completado": "2025-11-26T11:45:00"
|
||||||
|
},
|
||||||
|
"pdf_url": "https://minioapi.ayutec.es/pdfs/2025/11/inspeccion_123_ABC-123.pdf",
|
||||||
|
"firma": "data:image/png;base64,..."
|
||||||
|
},
|
||||||
|
"respuestas": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"pregunta": {
|
||||||
|
"id": 10,
|
||||||
|
"texto": "¿Estado de los neumáticos?",
|
||||||
|
"seccion": "Neumáticos",
|
||||||
|
"orden": 1
|
||||||
|
},
|
||||||
|
"respuesta": "ok",
|
||||||
|
"estado": "ok",
|
||||||
|
"comentario": "Neumáticos en buen estado",
|
||||||
|
"observaciones": "Presión correcta en las 4 ruedas",
|
||||||
|
"puntos_obtenidos": 1,
|
||||||
|
"es_critico": false,
|
||||||
|
"imagenes": [
|
||||||
|
{
|
||||||
|
"id": 100,
|
||||||
|
"url": "https://minioapi.ayutec.es/images/2025/11/foto1.jpg",
|
||||||
|
"filename": "neumatico_delantero.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 101,
|
||||||
|
"url": "https://minioapi.ayutec.es/images/2025/11/foto2.jpg",
|
||||||
|
"filename": "neumatico_trasero.jpg"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ai_analysis": {
|
||||||
|
"status": "ok",
|
||||||
|
"observations": "Los neumáticos presentan un desgaste uniforme...",
|
||||||
|
"recommendation": "Continuar con el mantenimiento preventivo",
|
||||||
|
"confidence": 0.95
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"pregunta": {
|
||||||
|
"id": 11,
|
||||||
|
"texto": "¿Luces delanteras funcionan?",
|
||||||
|
"seccion": "Iluminación",
|
||||||
|
"orden": 2
|
||||||
|
},
|
||||||
|
"respuesta": "warning",
|
||||||
|
"estado": "warning",
|
||||||
|
"comentario": "Faro izquierdo opaco",
|
||||||
|
"observaciones": "Requiere restauración de faro",
|
||||||
|
"puntos_obtenidos": 0.5,
|
||||||
|
"es_critico": true,
|
||||||
|
"imagenes": [
|
||||||
|
{
|
||||||
|
"id": 102,
|
||||||
|
"url": "https://minioapi.ayutec.es/images/2025/11/foto3.jpg",
|
||||||
|
"filename": "faro_izquierdo.jpg"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ai_analysis": {
|
||||||
|
"status": "minor",
|
||||||
|
"observations": "Se detecta opacidad en el faro izquierdo...",
|
||||||
|
"recommendation": "Pulir o restaurar el lente del faro",
|
||||||
|
"confidence": 0.9
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"timestamp": "2025-11-26T11:45:30.123456"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Campos Importantes
|
||||||
|
|
||||||
|
### Imágenes
|
||||||
|
- Cada respuesta incluye un array `imagenes` con:
|
||||||
|
- `id`: ID del archivo en la base de datos
|
||||||
|
- `url`: **URL directa** de la imagen en MinIO (lista para descargar/mostrar)
|
||||||
|
- `filename`: Nombre original del archivo
|
||||||
|
|
||||||
|
### AI Analysis
|
||||||
|
- Si la pregunta fue analizada por IA, incluye:
|
||||||
|
- `status`: ok/minor/critical
|
||||||
|
- `observations`: Observaciones del análisis
|
||||||
|
- `recommendation`: Recomendaciones
|
||||||
|
- `confidence`: Nivel de confianza (0-1)
|
||||||
|
|
||||||
|
### Código de Operario
|
||||||
|
- Se incluye en `inspeccion.mecanico.codigo_operario`
|
||||||
|
- Se copia automáticamente del perfil del mecánico al crear la inspección
|
||||||
|
|
||||||
|
### PDF
|
||||||
|
- URL del PDF generado en `inspeccion.pdf_url`
|
||||||
|
- Incluye miniaturas de todas las imágenes
|
||||||
|
|
||||||
|
## Uso en n8n
|
||||||
|
|
||||||
|
### 1. Webhook Trigger
|
||||||
|
Configura un nodo Webhook con la URL del archivo `.env`
|
||||||
|
|
||||||
|
### 2. Filtrar por tipo
|
||||||
|
```javascript
|
||||||
|
// Verificar si es una inspección completada
|
||||||
|
{{ $json.tipo === "inspeccion_completada" }}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Acceder a las imágenes
|
||||||
|
```javascript
|
||||||
|
// Obtener todas las URLs de imágenes
|
||||||
|
{{ $json.respuestas.map(r => r.imagenes.map(i => i.url)).flat() }}
|
||||||
|
|
||||||
|
// Primera imagen de cada respuesta
|
||||||
|
{{ $json.respuestas.map(r => r.imagenes[0]?.url) }}
|
||||||
|
|
||||||
|
// Imágenes de respuestas críticas
|
||||||
|
{{ $json.respuestas.filter(r => r.es_critico).map(r => r.imagenes).flat() }}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Descargar imágenes
|
||||||
|
Las URLs son públicas y directas, se pueden:
|
||||||
|
- Descargar con HTTP Request
|
||||||
|
- Enviar por email como adjuntos
|
||||||
|
- Procesar con Computer Vision
|
||||||
|
- Subir a otro servicio (Google Drive, Dropbox, etc.)
|
||||||
|
|
||||||
|
### 5. Ejemplo: Enviar por email
|
||||||
|
```javascript
|
||||||
|
// En un nodo Email
|
||||||
|
To: {{ $json.inspeccion.cliente_email }}
|
||||||
|
Subject: Inspección Completada - {{ $json.inspeccion.vehiculo.placa }}
|
||||||
|
Attachments: {{ $json.inspeccion.pdf_url }}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Logs
|
||||||
|
El backend imprime logs detallados:
|
||||||
|
```
|
||||||
|
🚀 Enviando inspección #123 a n8n...
|
||||||
|
📤 Enviando 15 respuestas con imágenes a n8n...
|
||||||
|
✅ Inspección #123 enviada exitosamente a n8n
|
||||||
|
- 15 respuestas
|
||||||
|
- 23 imágenes
|
||||||
|
```
|
||||||
|
|
||||||
|
## Seguridad
|
||||||
|
- El webhook es HTTPS
|
||||||
|
- Las URLs de imágenes son públicas en MinIO
|
||||||
|
- No se envían passwords ni tokens
|
||||||
|
- Se incluyen solo datos relevantes de la inspección
|
||||||
Reference in New Issue
Block a user