{
  "name": "TFG \u2014 Localizaci\u00f3n Visual Campus Tafira",
  "nodes": [
    {
      "id": "note-0001",
      "name": "Sticky Note \u2014 Recibir Foto",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        100,
        200
      ],
      "parameters": {
        "content": "## \ud83d\udce5 Recibir Foto\nWebhook que escucha `POST /localizar`.\n\nLa API Python ya proces\u00f3 la foto antes de llegar aqu\u00ed:\n- `body.foto_b64` \u2192 imagen en base64\n- `body.embedding` \u2192 vector DINOv2 de 1536 dimensiones\n\nEste nodo es la entrada del pipeline.",
        "width": 280,
        "height": 160
      }
    },
    {
      "id": "note-0002",
      "name": "Sticky Note \u2014 Buscar Visual",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        340,
        200
      ],
      "parameters": {
        "content": "## \ud83d\udd0d Buscar Visual\nLlama a `POST /buscar_visual`.\n\nEnv\u00eda el `embedding` a **Qdrant**, que busca las 5 celdas m\u00e1s similares por distancia coseno.\n\nDevuelve: lista de `candidatos` con `celda_id`, `orientacion` y `similitud` (0\u20131).",
        "width": 280,
        "height": 160
      }
    },
    {
      "id": "note-0003",
      "name": "Sticky Note \u2014 Detectar Elementos",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        580,
        200
      ],
      "parameters": {
        "content": "## \ud83c\udfaf Detectar Elementos\nLlama a `POST /detectar_elementos`.\n\nEnv\u00eda `foto_b64`. Internamente usa:\n- **YOLO11x** \u2192 detecta objetos est\u00e1ticos: sem\u00e1foros, se\u00f1ales de stop, bocas de incendio, bancos, parqu\u00edmetros\n\nDevuelve: lista de `elementos` detectados con su posici\u00f3n y distancia estimada.",
        "width": 280,
        "height": 170
      }
    },
    {
      "id": "note-0005",
      "name": "Sticky Note \u2014 Subinterpolacion",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        820,
        200
      ],
      "parameters": {
        "content": "## \ud83d\udcd0 Subinterpolaci\u00f3n (top-K)\nLlama a `POST /subinterpolacion`.\n\nSe le pasan **los 5 candidatos top-K** del nodo anterior. El endpoint itera por todos, consulta BD4 para cada uno y consolida los desplazamientos con mediana ponderada por similitud.\n\nResultado: estimaci\u00f3n robusta de la celda real del usuario aunque ninguna celda de referencia coincida exactamente con la suya.",
        "width": 280,
        "height": 180
      }
    },
    {
      "id": "note-0006",
      "name": "Sticky Note \u2014 Descripcion Referencia",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        1060,
        200
      ],
      "parameters": {
        "content": "## \ud83d\udcd6 Descripci\u00f3n Referencia\n`GET /descripcion_referencia/{celda}/{orientacion}`\n\nConsulta **BD2** para obtener la descripci\u00f3n textual que fue guardada cuando se registr\u00f3 la foto de referencia de esa celda.\n\nUsada como contexto adicional para gemma4:26b.",
        "width": 280,
        "height": 155
      }
    },
    {
      "id": "note-0009",
      "name": "Sticky Note \u2014 Descripcion Gemma4",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        1300,
        200
      ],
      "parameters": {
        "content": "## \ud83e\udd16 Descripci\u00f3n Gemma4\nLlama a `POST /generar_descripcion`.\n\nEl modelo **gemma4:26b** (multimodal MoE, corre en Ollama) recibe la foto y el contexto (celda estimada, confianza, descripci\u00f3n de referencia) y genera una **descripci\u00f3n en lenguaje natural** del lugar donde est\u00e1 el usuario.",
        "width": 280,
        "height": 165
      }
    },
    {
      "id": "note-0010",
      "name": "Sticky Note \u2014 Generar Mapa",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        1540,
        200
      ],
      "parameters": {
        "content": "## \ud83d\uddfa\ufe0f Generar Mapa\nLlama a `POST /generar_mapa`.\n\nCrea un mapa **HTML interactivo** con Folium + OpenStreetMap:\n- Celda estimada \u2192 pol\u00edgono + marcador **azul** (\u00fanica capa)\n\nDevuelve el HTML como string en `mapa_html`.",
        "width": 280,
        "height": 155
      }
    },
    {
      "id": "note-0011",
      "name": "Sticky Note \u2014 Preparar Respuesta",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        1780,
        200
      ],
      "parameters": {
        "content": "## \ud83e\udde9 Preparar Respuesta\nNodo de c\u00f3digo JavaScript.\n\nRe\u00fane los resultados de todos los nodos anteriores y construye el **JSON final** con:\n`celda_estimada`, `confianza_vpr`, `confianza_bd4`, `desplazamiento_m`, `respuesta` (texto gemma4), `mapa_html`, `candidatas`, `razonamiento`.",
        "width": 280,
        "height": 170
      }
    },
    {
      "id": "note-0012",
      "name": "Sticky Note \u2014 Responder",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        2020,
        200
      ],
      "parameters": {
        "content": "## \ud83d\udce4 Responder\nDevuelve el JSON ensamblado al webhook de **FastAPI** que estaba esperando la respuesta.\n\nEste es el \u00faltimo nodo del pipeline. FastAPI recibe el resultado y lo env\u00eda al navegador del usuario.",
        "width": 280,
        "height": 140
      }
    },
    {
      "id": "a1b2c3d4-0001-0001-0001-000000000001",
      "name": "Recibir Foto",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 1.1,
      "position": [
        200,
        400
      ],
      "parameters": {
        "httpMethod": "POST",
        "path": "localizar",
        "responseMode": "lastNode",
        "options": {}
      }
    },
    {
      "id": "a1b2c3d4-0002-0002-0002-000000000002",
      "name": "Buscar Visual",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        440,
        400
      ],
      "parameters": {
        "method": "POST",
        "url": "http://api:8000/buscar_visual",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "contentType": "raw",
        "rawContentType": "application/json",
        "body": "={{ JSON.stringify({ embedding: $json.body.embedding, n_resultados: 5 }) }}"
      }
    },
    {
      "id": "a1b2c3d4-0003-0003-0003-000000000003",
      "name": "Detectar Elementos",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        680,
        400
      ],
      "parameters": {
        "method": "POST",
        "url": "http://api:8000/detectar_elementos",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "contentType": "raw",
        "rawContentType": "application/json",
        "body": "={{ JSON.stringify({ foto_b64: $('Recibir Foto').item.json.body.foto_b64 }) }}"
      }
    },
    {
      "id": "a1b2c3d4-0005-0005-0005-000000000005",
      "name": "Subinterpolacion",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        920,
        400
      ],
      "parameters": {
        "method": "POST",
        "url": "http://api:8000/subinterpolacion",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "contentType": "raw",
        "rawContentType": "application/json",
        "body": "={{ JSON.stringify({ celda_ref: $('Buscar Visual').item.json.candidatos[0].celda_id, orientacion: $('Buscar Visual').item.json.candidatos[0].orientacion, elementos_usuario: $('Detectar Elementos').item.json.elementos, similitud: $('Buscar Visual').item.json.candidatos[0].similitud, candidatos: $('Buscar Visual').item.json.candidatos, focal_usuario: $('Recibir Foto').item.json.body.focal_usuario }) }}"
      }
    },
    {
      "id": "a1b2c3d4-0006-0006-0006-000000000006",
      "name": "Descripcion Referencia",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1160,
        400
      ],
      "parameters": {
        "method": "GET",
        "url": "={{ 'http://api:8000/descripcion_referencia/' + $('Buscar Visual').item.json.candidatos[0].celda_id + '/' + $('Buscar Visual').item.json.candidatos[0].orientacion }}"
      }
    },
    {
      "id": "a1b2c3d4-0009-0009-0009-000000000009",
      "name": "Descripcion Gemma4",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1400,
        400
      ],
      "parameters": {
        "method": "POST",
        "url": "http://api:8000/generar_descripcion",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "contentType": "raw",
        "rawContentType": "application/json",
        "body": "={{ JSON.stringify({ foto_b64: $('Recibir Foto').item.json.body.foto_b64, celda_estimada: $('Subinterpolacion').item.json.celda_estimada, celda_ref: $('Buscar Visual').item.json.candidatos[0].celda_id, desplazamiento_m: $('Subinterpolacion').item.json.desplazamiento_m || 0, confianza_vpr: $('Buscar Visual').item.json.candidatos[0].similitud, confianza_bd4: $('Subinterpolacion').item.json.confianza_bd4 || 0, descripcion_lugar: $('Descripcion Referencia').item.json.descripcion || '' }) }}"
      }
    },
    {
      "id": "a1b2c3d4-0010-0010-0010-000000000010",
      "name": "Generar Mapa",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1640,
        400
      ],
      "parameters": {
        "method": "POST",
        "url": "http://api:8000/generar_mapa",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "contentType": "raw",
        "rawContentType": "application/json",
        "body": "={{ JSON.stringify({ celda_estimada: $('Subinterpolacion').item.json.celda_estimada, celda_ref: $('Buscar Visual').item.json.candidatos[0].celda_id, candidatas: $('Buscar Visual').item.json.candidatos.map(c => c.celda_id) }) }}"
      }
    },
    {
      "id": "a1b2c3d4-0011-0011-0011-000000000011",
      "name": "Preparar Respuesta",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1880,
        400
      ],
      "parameters": {
        "jsCode": "const buscarVisual  = $('Buscar Visual').item.json;\nconst subinterp     = $('Subinterpolacion').item.json;\nconst descGemma4  = $('Descripcion Gemma4').item.json;\nconst mapa          = $('Generar Mapa').item.json;\n\nconst top = buscarVisual.candidatos[0];\n\nreturn [{\n  json: {\n    celda_estimada   : subinterp.celda_estimada || top.celda_id,\n    celda_ref        : top.celda_id,\n    orientacion      : top.orientacion,\n    confianza_vpr    : top.similitud,\n    confianza_bd4    : subinterp.confianza_bd4 || 0,\n    desplazamiento_m : subinterp.desplazamiento_m || 0,\n    respuesta        : descGemma4.descripcion || '',\n    mapa_html        : mapa.mapa_html || '',\n    candidatas       : buscarVisual.candidatos.map(c => c.celda_id),\n    detalle_bd4      : subinterp.detalle || [],\n    razonamiento     : top.similitud >= 0.5 ? 'Alta confianza VPR' : 'Baja confianza VPR \u2014 resultado aproximado',\n    fuera_de_campus  : buscarVisual.fuera_de_campus || false,\n    similitud_top    : top.similitud\n  }\n}];"
      }
    },
    {
      "id": "a1b2c3d4-0012-0012-0012-000000000012",
      "name": "Responder",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        2120,
        400
      ],
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ $json }}",
        "options": {
          "responseHeaders": {
            "entries": [
              {
                "name": "Content-Type",
                "value": "application/json"
              }
            ]
          }
        }
      }
    }
  ],
  "connections": {
    "Recibir Foto": {
      "main": [
        [
          {
            "node": "Buscar Visual",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Buscar Visual": {
      "main": [
        [
          {
            "node": "Detectar Elementos",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Detectar Elementos": {
      "main": [
        [
          {
            "node": "Subinterpolacion",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Subinterpolacion": {
      "main": [
        [
          {
            "node": "Descripcion Referencia",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Descripcion Referencia": {
      "main": [
        [
          {
            "node": "Descripcion Gemma4",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Descripcion Gemma4": {
      "main": [
        [
          {
            "node": "Generar Mapa",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generar Mapa": {
      "main": [
        [
          {
            "node": "Preparar Respuesta",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Preparar Respuesta": {
      "main": [
        [
          {
            "node": "Responder",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": true,
  "settings": {
    "executionOrder": "v1",
    "saveManualExecutions": true,
    "callerPolicy": "workflowsFromSameOwner",
    "errorWorkflow": ""
  },
  "meta": {
    "templateCredsSetupCompleted": true
  }
}