===========================================
GUÍA DE INTEGRACIÓN SUPABASE VECTOR
Haka Playwright Engine v1.3.23
===========================================

🔄 CAMBIO IMPORTANTE EN v1.3.23
--------------------------------
Los pasos de búsqueda en Supabase ahora cargan automáticamente el contexto en Gemini.
Ya NO necesitas archivos .txt adicionales para usar RAG con validación semántica.

ANTES (v1.3.22):
  Given el contexto está cargado desde el archivo "context.txt"
  When busco en la tabla "knowledge_base" el contexto más similar a "query" y lo guardo en "context"
  Then valido con Gemini...

AHORA (v1.3.23):
  When busco en la tabla "knowledge_base" el contexto más similar a "query" y lo guardo en "context"
  Then valido con Gemini...  # ✅ El contexto ya está cargado automáticamente

===========================================

DESCRIPCIÓN
-----------
Esta guía explica cómo usar los steps de Supabase Vector Database para realizar
búsquedas semánticas en bases de datos vectorizadas y cargar contexto dinámico
para testing de IA Generativa.

CASOS DE USO
------------
✅ Búsqueda semántica en knowledge bases
✅ RAG (Retrieval Augmented Generation) testing
✅ Carga dinámica de contexto para Gemini
✅ Testing de sistemas con bases de datos vectoriales
✅ Validación de respuestas basadas en documentación remota

===========================================
REQUISITOS PREVIOS
===========================================

PASO 1: Instalar Dependencias
------------------------------
pip install supabase google-genai

⚠️ IMPORTANTE: Asegúrate de tener instalada la versión correcta de google-genai
Si tienes problemas con embeddings, actualiza:
pip install --upgrade google-genai

PASO 2: Configurar Variables de Entorno
----------------------------------------
Agregar al archivo .env:

# Supabase
SUPABASE_URL=https://tu-proyecto.supabase.co
SUPABASE_KEY=tu_api_key_aqui

# Gemini (para generar embeddings)
GEMINI_API_KEY=tu_gemini_api_key

PASO 3: Configurar Base de Datos en Supabase
---------------------------------------------

⚠️ IMPORTANTE: Primero debes habilitar la extensión pgvector

0. Habilitar la extensión pgvector (REQUERIDO):

-- Ejecutar PRIMERO en el SQL Editor de Supabase
CREATE EXTENSION IF NOT EXISTS vector;

1. Crear tabla con columna de embeddings:

CREATE TABLE knowledge_base (
  id BIGSERIAL PRIMARY KEY,
  content TEXT,
  metadata JSONB,
  embedding VECTOR(768)  -- Gemini gemini-embedding-001 usa 768 dimensiones por defecto
);

2. Crear función de búsqueda vectorial:

CREATE OR REPLACE FUNCTION match_documents(
  query_embedding VECTOR(768),
  match_threshold FLOAT,
  match_count INT
)
RETURNS TABLE (
  id BIGINT,
  content TEXT,
  metadata JSONB,
  similarity FLOAT
)
LANGUAGE SQL STABLE
AS $$
  SELECT
    id,
    content,
    metadata,
    1 - (embedding <=> query_embedding) AS similarity
  FROM knowledge_base
  WHERE 1 - (embedding <=> query_embedding) > match_threshold
  ORDER BY similarity DESC
  LIMIT match_count;
$$;

3. Crear índice para búsqueda rápida:

CREATE INDEX ON knowledge_base 
USING ivfflat (embedding vector_cosine_ops)
WITH (lists = 100);

===========================================
STEPS DISPONIBLES - REFERENCIA COMPLETA
===========================================

Total de Steps: 10 funciones (20 variantes con Given/When)

===========================================
CATEGORÍA 1: CONEXIÓN A SUPABASE
===========================================

STEP 1.1: Conexión con Credenciales Explícitas
-----------------------------------------------

SINTAXIS:
  Given me conecto a Supabase con URL "{supabase_url}" y key "{supabase_key}"
  When me conecto a Supabase con URL "{supabase_url}" y key "{supabase_key}"

PARÁMETROS:
  - supabase_url: URL del proyecto Supabase (ej: https://xxx.supabase.co)
  - supabase_key: API key de Supabase (anon o service_role)

DESCRIPCIÓN:
  Establece conexión con Supabase usando credenciales proporcionadas directamente.
  Soporta resolución de variables usando ${VARIABLE} o context.variable_manager.

CUÁNDO USAR:
  - Cuando necesitas conectar a diferentes proyectos Supabase
  - Para testing con múltiples ambientes
  - Cuando las credenciales vienen de variables dinámicas

EJEMPLO:
  Given me conecto a Supabase con URL "${SUPABASE_URL}" y key "${SUPABASE_KEY}"
  Given me conecto a Supabase con URL "https://myproject.supabase.co" y key "eyJhbGc..."

RESULTADO:
  - Crea context.supabase_client disponible para otros steps
  - Imprime mensaje de confirmación con la URL conectada

ERRORES COMUNES:
  - URL incorrecta o sin https://
  - API key inválida o expirada
  - Proyecto Supabase pausado o eliminado


STEP 1.2: Conexión con Variables de Entorno
--------------------------------------------

SINTAXIS:
  Given me conecto a Supabase usando variables de entorno
  When me conecto a Supabase usando variables de entorno

PARÁMETROS:
  Ninguno (usa variables de entorno)

VARIABLES REQUERIDAS:
  - SUPABASE_URL: URL del proyecto
  - SUPABASE_KEY: API key

DESCRIPCIÓN:
  Conecta a Supabase usando variables de entorno del archivo .env.
  Es la forma recomendada para mantener credenciales seguras.

CUÁNDO USAR:
  - Para la mayoría de los casos (recomendado)
  - Cuando las credenciales están en .env
  - Para mantener seguridad en el código

EJEMPLO:
  Given me conecto a Supabase usando variables de entorno

RESULTADO:
  - Lee SUPABASE_URL y SUPABASE_KEY del .env
  - Crea context.supabase_client
  - Imprime confirmación de conexión

ERRORES COMUNES:
  - Variables no definidas en .env
  - Archivo .env no cargado
  - Nombres de variables incorrectos


===========================================
CATEGORÍA 2: BÚSQUEDA VECTORIAL
===========================================

STEP 2.1: Búsqueda Vectorial Simple
------------------------------------

SINTAXIS:
  When busco en la tabla "{table_name}" el contexto más similar a "{query}" y lo guardo en "{variable_name}"
  Given busco en la tabla "{table_name}" el contexto más similar a "{query}" y lo guardo en "{variable_name}"

PARÁMETROS:
  - table_name: Nombre de la tabla con embeddings
  - query: Texto de búsqueda (pregunta o tema)
  - variable_name: Variable donde guardar el resultado

DESCRIPCIÓN:
  Realiza búsqueda semántica en Supabase usando embeddings de Gemini.
  Genera embedding de la query, busca documentos similares y concatena los resultados.
  Usa la función RPC match_documents con umbral 0.7 y retorna 5 documentos.

PROCESO INTERNO:
  1. Genera embedding de la query con Gemini (text-embedding-004)
  2. Llama a función RPC match_documents en Supabase
  3. Concatena el contenido de los documentos encontrados
  4. Guarda el texto concatenado en la variable

CUÁNDO USAR:
  - Para obtener contexto relevante de una knowledge base
  - RAG (Retrieval Augmented Generation)
  - Búsqueda semántica de documentación
  - Cuando necesitas el contexto como texto plano

EJEMPLO:
  When busco en la tabla "knowledge_base" el contexto más similar a "política de devoluciones" y lo guardo en "context"
  When busco en la tabla "faq" el contexto más similar a "${user_question}" y lo guardo en "relevant_info"

RESULTADO:
  - Variable contiene texto concatenado de documentos similares
  - Imprime número de documentos encontrados
  - Si no encuentra nada, guarda string vacío ""

CONFIGURACIÓN:
  - Umbral de similitud: 0.7 (hardcoded)
  - Número de resultados: 5 (hardcoded)
  - Modelo de embeddings: text-embedding-004 (768 dimensiones)

ERRORES COMUNES:
  - Tabla no existe
  - Función match_documents no creada
  - GEMINI_API_KEY no definida
  - Columna embedding no es VECTOR(768)


STEP 2.2: Búsqueda Vectorial Múltiple
--------------------------------------

SINTAXIS:
  When busco en la tabla "{table_name}" los {count} contextos más similares a "{query}" y los guardo en "{variable_name}"
  Given busco en la tabla "{table_name}" los {count} contextos más similares a "{query}" y los guardo en "{variable_name}"

PARÁMETROS:
  - table_name: Nombre de la tabla con embeddings
  - count: Número de documentos a retornar (entero)
  - query: Texto de búsqueda
  - variable_name: Variable donde guardar los resultados

DESCRIPCIÓN:
  Similar a búsqueda simple, pero retorna una lista de documentos en lugar de texto concatenado.
  Permite especificar cuántos documentos retornar.
  Útil cuando necesitas procesar documentos individualmente.

PROCESO INTERNO:
  1. Genera embedding de la query con Gemini
  2. Llama a match_documents con match_count personalizado
  3. Guarda lista completa de documentos (con metadata)
  4. Cada documento incluye: id, content, metadata, similarity

CUÁNDO USAR:
  - Cuando necesitas procesar documentos por separado
  - Para mostrar múltiples fuentes al usuario
  - Cuando necesitas metadata de cada documento
  - Para análisis de relevancia individual

EJEMPLO:
  When busco en la tabla "knowledge_base" los 3 contextos más similares a "métodos de pago" y los guardo en "payment_docs"
  When busco en la tabla "faq" los 10 contextos más similares a "${query}" y los guardo en "top_results"

RESULTADO:
  - Variable contiene lista de diccionarios
  - Cada elemento tiene: id, content, metadata, similarity
  - Si no encuentra nada, guarda lista vacía []
  - Imprime número de documentos encontrados

ESTRUCTURA DE RESULTADO:
  [
    {
      "id": 1,
      "content": "Texto del documento...",
      "metadata": {"filename": "doc.txt", "chunk_id": 1},
      "similarity": 0.85
    },
    ...
  ]

ERRORES COMUNES:
  - count no es un número válido
  - Mismos errores que búsqueda simple


STEP 2.3: Carga Directa en Gemini
----------------------------------

SINTAXIS:
  Given cargo el contexto de Supabase tabla "{table_name}" para la query "{query}" en el contexto de Gemini "{context_name}"
  When cargo el contexto de Supabase tabla "{table_name}" para la query "{query}" en el contexto de Gemini "{context_name}"

PARÁMETROS:
  - table_name: Tabla de Supabase con embeddings
  - query: Query de búsqueda
  - context_name: Nombre del contexto para Gemini

DESCRIPCIÓN:
  Búsqueda vectorial + carga automática en Gemini en un solo paso.
  Busca documentos relevantes en Supabase y los carga como contexto de negocio en Gemini.
  Integración directa entre Supabase Vector y Gemini Judge.

PROCESO INTERNO:
  1. Genera embedding de la query
  2. Busca en Supabase (5 documentos, umbral 0.7)
  3. Concatena documentos encontrados
  4. Crea archivo temporal con el contenido
  5. Llama al step de Gemini para cargar contexto
  6. Limpia archivo temporal

CUÁNDO USAR:
  - Para RAG testing con Gemini como Juez
  - Cuando necesitas validar respuestas contra knowledge base
  - Testing de chatbots con contexto dinámico
  - Validación de respuestas de IA

EJEMPLO:
  Given cargo el contexto de Supabase tabla "knowledge_base" para la query "políticas de atención" en el contexto de Gemini "customer_service"
  Given cargo el contexto de Supabase tabla "docs" para la query "API authentication" en el contexto de Gemini "api_docs"

RESULTADO:
  - Contexto cargado en Gemini con el nombre especificado
  - Disponible para validaciones con Gemini Judge
  - Imprime número de documentos cargados
  - Contexto persiste durante el escenario

USO POSTERIOR:
  Then el Juez Gemini debe validar la respuesta con un umbral mínimo de 0.8
  Then valido que la respuesta no contiene alucinaciones según el contexto

ERRORES COMUNES:
  - No se encontró contexto relevante (AssertionError)
  - Step de Gemini no disponible
  - Mismos errores de búsqueda vectorial


===========================================
CATEGORÍA 3: CONSULTAS DIRECTAS A TABLAS
===========================================

STEP 3.1: Consulta Completa de Tabla
-------------------------------------

SINTAXIS:
  When consulto la tabla "{table_name}" en Supabase y guardo el resultado en "{variable_name}"
  Given consulto la tabla "{table_name}" en Supabase y guardo el resultado en "{variable_name}"

PARÁMETROS:
  - table_name: Nombre de la tabla
  - variable_name: Variable donde guardar resultados

DESCRIPCIÓN:
  Consulta todos los registros de una tabla de Supabase.
  Equivalente a SELECT * FROM table_name.
  No usa búsqueda vectorial, es consulta SQL directa.

PROCESO INTERNO:
  1. Ejecuta supabase.table(table_name).select("*").execute()
  2. Guarda result.data en la variable
  3. Imprime número de registros encontrados

CUÁNDO USAR:
  - Para obtener todos los registros de una tabla
  - Testing de datos maestros
  - Validación de catálogos completos
  - Cuando no necesitas búsqueda semántica

EJEMPLO:
  When consulto la tabla "products" en Supabase y guardo el resultado en "all_products"
  When consulto la tabla "users" en Supabase y guardo el resultado en "user_list"

RESULTADO:
  - Variable contiene lista de diccionarios
  - Cada elemento es un registro completo de la tabla
  - Incluye todas las columnas
  - Si tabla vacía, retorna []

ESTRUCTURA DE RESULTADO:
  [
    {"id": 1, "name": "Product A", "price": 100},
    {"id": 2, "name": "Product B", "price": 200},
    ...
  ]

LIMITACIONES:
  - No tiene paginación (carga todos los registros)
  - Puede ser lento con tablas grandes
  - No filtra ni ordena

ERRORES COMUNES:
  - Tabla no existe
  - Sin permisos de lectura en la tabla
  - Tabla muy grande (timeout)


STEP 3.2: Consulta con Filtro
------------------------------

SINTAXIS:
  When consulto la tabla "{table_name}" filtrando por "{column}" igual a "{value}" y guardo en "{variable_name}"
  Given consulto la tabla "{table_name}" filtrando por "{column}" igual a "{value}" y guardo en "{variable_name}"

PARÁMETROS:
  - table_name: Nombre de la tabla
  - column: Columna para filtrar
  - value: Valor del filtro
  - variable_name: Variable donde guardar resultados

DESCRIPCIÓN:
  Consulta registros de una tabla con filtro WHERE.
  Equivalente a SELECT * FROM table_name WHERE column = value.
  Usa operador de igualdad exacta (.eq()).

PROCESO INTERNO:
  1. Resuelve variables en table_name, column y value
  2. Ejecuta supabase.table(table_name).select("*").eq(column, value).execute()
  3. Guarda result.data en la variable
  4. Imprime número de registros encontrados

CUÁNDO USAR:
  - Para filtrar registros por un criterio específico
  - Testing de datos por categoría
  - Validación de registros específicos
  - Búsqueda por ID, categoría, estado, etc.

EJEMPLO:
  When consulto la tabla "products" filtrando por "category" igual a "electronics" y guardo en "electronics"
  When consulto la tabla "users" filtrando por "status" igual a "active" y guardo en "active_users"
  When consulto la tabla "orders" filtrando por "user_id" igual a "${current_user_id}" y guardo en "user_orders"

RESULTADO:
  - Variable contiene lista de diccionarios filtrados
  - Solo registros que cumplen el criterio
  - Si no hay coincidencias, retorna []

ESTRUCTURA DE RESULTADO:
  [
    {"id": 1, "name": "Product A", "category": "electronics", "price": 100},
    {"id": 3, "name": "Product C", "category": "electronics", "price": 300},
    ...
  ]

LIMITACIONES:
  - Solo soporta igualdad exacta (=)
  - No soporta operadores como >, <, LIKE, IN
  - Un solo filtro por consulta
  - Para filtros complejos, usar consultas SQL directas

OPERADORES NO SOPORTADOS:
  - Mayor que (>)
  - Menor que (<)
  - LIKE / ILIKE
  - IN / NOT IN
  - IS NULL / IS NOT NULL
  - Múltiples condiciones AND/OR

ERRORES COMUNES:
  - Columna no existe en la tabla
  - Tipo de dato incompatible (ej: filtrar número con texto)
  - Sin permisos de lectura

===========================================
CASOS DE USO Y EJEMPLOS
===========================================

CASO 1: RAG Testing Básico
---------------------------

OBJETIVO: Validar que un chatbot responde correctamente usando knowledge base

Feature: Testing de RAG con Supabase

  Background:
    Given me conecto a Supabase usando variables de entorno

  Scenario: Validar respuesta basada en knowledge base
    # 1. Buscar contexto relevante
    When busco en la tabla "knowledge_base" el contexto más similar a "política de devoluciones" y lo guardo en "kb_context"
    Then verifico que la variable "kb_context" no está vacía
    
    # 2. Hacer pregunta al sistema con el contexto
    When realizo una petición POST a "${API_URL}/chat" con el body:
      """
      {
        "question": "¿Cuál es la política de devoluciones?",
        "context": "${kb_context}"
      }
      """
    Then el código de estado de la respuesta debería ser 200
    And extraigo el valor "answer" del JSON de respuesta y lo guardo en "chatbot_answer"
    
    # 3. Validar con Gemini
    When establezco la respuesta del SUT como "${chatbot_answer}"
    And cargo la variable "kb_context" como contexto de Gemini "return_policy"
    Then el Juez Gemini debe validar la respuesta con un umbral mínimo de 0.85
    And valido que la respuesta no contiene alucinaciones según el contexto


CASO 2: Carga Directa en Gemini
--------------------------------

OBJETIVO: Cargar contexto de Supabase directamente en Gemini para validación

Feature: Testing con Contexto Dinámico

  @no_browser
  Scenario: Validar respuestas con contexto de Supabase
    # Cargar contexto directamente desde Supabase a Gemini
    Given cargo el contexto de Supabase tabla "knowledge_base" para la query "políticas de atención al cliente" en el contexto de Gemini "customer_service"
    
    # Obtener respuesta del sistema
    When realizo una petición POST a "${CHATBOT_API}/ask" con el body:
      """
      {
        "question": "¿Cuál es el horario de atención?"
      }
      """
    And extraigo el valor "response" del JSON de respuesta y lo guardo en "chatbot_response"
    
    # Validar con Gemini
    When establezco la respuesta del SUT como "${chatbot_response}"
    Then el Juez Gemini debe validar la respuesta con un umbral mínimo de 0.85
    And valido que la respuesta no contiene alucinaciones según el contexto


CASO 3: Búsqueda Múltiple y Validación
---------------------------------------

OBJETIVO: Validar que el sistema usa información de múltiples documentos

Feature: Validación de Múltiples Fuentes

  Background:
    Given me conecto a Supabase usando variables de entorno

  Scenario: Validar consistencia entre múltiples documentos
    # Buscar múltiples documentos relevantes
    When busco en la tabla "knowledge_base" los 3 contextos más similares a "política de envíos" y los guardo en "shipping_docs"
    Then verifico que la variable "shipping_docs" es una lista
    
    # Concatenar documentos para contexto
    When concateno los contenidos de "shipping_docs" y lo guardo en "shipping_context"
    
    # Validar respuesta del sistema
    When realizo una petición POST a "${API_URL}/query" con el body:
      """
      {
        "question": "¿Cuánto tarda el envío?",
        "documents": ${shipping_docs}
      }
      """
    Then el código de estado de la respuesta debería ser 200
    And extraigo el valor "answer" del JSON de respuesta y lo guardo en "answer"
    
    # Validar con Gemini
    When establezco la respuesta del SUT como "${answer}"
    And cargo la variable "shipping_context" como contexto de Gemini "shipping"
    Then el Juez Gemini debe validar la respuesta con un umbral mínimo de 0.8


CASO 4: Testing de Catálogo con Filtros
----------------------------------------

OBJETIVO: Validar que la API retorna los mismos productos que la base de datos

Feature: Validación de Catálogo de Productos

  Background:
    Given me conecto a Supabase usando variables de entorno

  Scenario: Validar productos por categoría
    # Consultar productos de una categoría
    When consulto la tabla "products" filtrando por "category" igual a "electronics" y guardo en "db_electronics"
    
    # Validar que la API retorna los mismos productos
    When realizo una petición GET a "${API_URL}/products?category=electronics"
    Then el código de estado de la respuesta debería ser 200
    And extraigo el valor "products" del JSON de respuesta y lo guardo en "api_electronics"
    
    # Comparar resultados
    Then verifico que la variable "api_electronics" tiene el mismo número de elementos que "db_electronics"


CASO 5: Validación de Datos Maestros
-------------------------------------

OBJETIVO: Verificar integridad de datos en tablas completas

Feature: Validación de Datos Maestros

  Background:
    Given me conecto a Supabase usando variables de entorno

  Scenario: Verificar integridad de knowledge base
    # Consultar toda la tabla
    When consulto la tabla "knowledge_base" en Supabase y guardo el resultado en "all_docs"
    
    # Validaciones
    Then verifico que la variable "all_docs" es una lista
    And verifico que la variable "all_docs" tiene al menos 10 elementos
    
    # Verificar que todos tienen embeddings
    Then verifico que todos los elementos de "all_docs" tienen la propiedad "embedding"

===========================================
CONFIGURACIÓN Y PERSONALIZACIÓN
===========================================

PERSONALIZAR FUNCIÓN DE BÚSQUEDA
---------------------------------

Si tu función RPC tiene un nombre diferente a 'match_documents':

UBICACIÓN: hakalab_framework/steps/supabase_vector_steps.py

LÍNEAS A MODIFICAR:
  # En step_search_vector_context (línea ~95)
  # En step_search_multiple_vector_contexts (línea ~150)
  # En step_load_supabase_context_to_gemini (línea ~200)

CAMBIO:
  result = context.supabase_client.rpc(
      'tu_funcion_personalizada',  # <-- Cambiar aquí
      {
          'query_embedding': query_embedding,
          'match_threshold': 0.7,
          'match_count': 5
      }
  ).execute()

NOTA: Tu función debe aceptar los mismos parámetros y retornar la misma estructura.


AJUSTAR UMBRAL DE SIMILITUD
----------------------------

El umbral por defecto es 0.7 (70% de similitud).

VALORES RECOMENDADOS:
  - 0.5-0.6: Búsqueda amplia, más resultados
  - 0.7: Balance (default)
  - 0.8-0.9: Búsqueda estricta, solo muy similares

CÓMO MODIFICAR:
  UBICACIÓN: hakalab_framework/steps/supabase_vector_steps.py
  
  CAMBIAR:
    'match_threshold': 0.7,  # <-- Modificar este valor
  
  POR:
    'match_threshold': 0.8,  # Más estricto

ALTERNATIVA: Crear step personalizado con umbral configurable


AJUSTAR NÚMERO DE RESULTADOS
-----------------------------

Por defecto retorna 5 documentos en búsqueda simple.

CÓMO MODIFICAR:
  UBICACIÓN: hakalab_framework/steps/supabase_vector_steps.py
  
  EN step_search_vector_context (línea ~95):
    'match_count': 5  # <-- Cambiar a 3, 10, etc.
  
  EN step_load_supabase_context_to_gemini (línea ~200):
    'match_count': 5  # <-- Cambiar según necesidad

NOTA: Para búsqueda múltiple, el count es parámetro del step.


CAMBIAR MODELO DE EMBEDDINGS
-----------------------------

Por defecto usa "text-embedding-004" de Gemini (768 dimensiones).

MODELOS DISPONIBLES:
  - text-embedding-004: 768 dim (recomendado, más reciente)
  - embedding-001: 768 dim (legacy)

CÓMO MODIFICAR:
  UBICACIÓN: hakalab_framework/steps/supabase_vector_steps.py
  
  BUSCAR (aparece 3 veces):
    result = genai.embed_content(
        model="models/text-embedding-004",  # <-- Cambiar aquí
        content=resolved_query,
        task_type="retrieval_query"
    )

IMPORTANTE: Si cambias el modelo, debes:
  1. Regenerar TODOS los embeddings en Supabase
  2. Ajustar dimensión de columna si es diferente
  3. Recrear índices vectoriales


TASK TYPES DISPONIBLES
-----------------------

El parámetro task_type optimiza el embedding para diferentes usos:

PARA QUERIES (búsquedas):
  task_type="retrieval_query"  # Optimizado para buscar

PARA DOCUMENTOS (almacenar):
  task_type="retrieval_document"  # Optimizado para indexar

OTROS:
  - "semantic_similarity": Comparación de similitud
  - "classification": Clasificación de texto
  - "clustering": Agrupación de documentos

UBICACIÓN EN EL CÓDIGO:
  - Queries: step_search_vector_context (usa "retrieval_query")
  - Documentos: vectorize_documents.py (usa "retrieval_document")


PERSONALIZAR METADATA
---------------------

Puedes agregar metadata personalizada al insertar documentos.

EJEMPLO EN vectorize_documents.py:
  metadata = {
      'filename': file_path.name,
      'chunk_id': i,
      'total_chunks': len(chunks),
      'chunk_size': len(chunk.split()),
      'file_path': str(file_path),
      # AGREGAR CAMPOS PERSONALIZADOS:
      'category': 'faq',
      'language': 'es',
      'author': 'QA Team',
      'version': '1.0',
      'last_updated': '2026-03-01'
  }

USO POSTERIOR:
  Puedes filtrar por metadata en consultas SQL:
  
  SELECT * FROM knowledge_base 
  WHERE metadata->>'category' = 'faq'
  AND metadata->>'language' = 'es';


CONFIGURAR ÍNDICES VECTORIALES
-------------------------------

TIPOS DE ÍNDICES:

1. IVFFLAT (recomendado para <1M vectores):
   CREATE INDEX ON knowledge_base 
   USING ivfflat (embedding vector_cosine_ops)
   WITH (lists = 100);
   
   PARÁMETRO lists:
   - Tablas pequeñas (<10K): lists = 10-50
   - Tablas medianas (10K-100K): lists = 100
   - Tablas grandes (>100K): lists = 1000

2. HNSW (recomendado para >1M vectores):
   CREATE INDEX ON knowledge_base 
   USING hnsw (embedding vector_cosine_ops)
   WITH (m = 16, ef_construction = 64);
   
   PARÁMETROS:
   - m: Número de conexiones (default: 16)
   - ef_construction: Calidad del índice (default: 64)

OPERADORES DE DISTANCIA:
  - vector_cosine_ops: Similitud coseno (recomendado)
  - vector_l2_ops: Distancia euclidiana
  - vector_ip_ops: Producto interno


OPTIMIZACIÓN DE PERFORMANCE
----------------------------

1. CHUNKING ÓPTIMO:
   - Documentos técnicos: 500-800 palabras
   - FAQs: 300-500 palabras
   - Artículos: 600-1000 palabras
   - Overlap: 10-15% del chunk_size

2. CACHÉ DE EMBEDDINGS:
   - Genera embeddings UNA VEZ
   - Almacena en Supabase
   - No regeneres en cada búsqueda

3. BATCH PROCESSING:
   - Procesa múltiples documentos en lote
   - Usa el script vectorize_documents.py
   - Evita llamadas individuales

4. ÍNDICES:
   - Crea índices vectoriales DESPUÉS de cargar datos
   - Usa IVFFLAT para tablas <1M vectores
   - Usa HNSW para tablas >1M vectores

5. PAGINACIÓN:
   - Para tablas grandes, usa LIMIT y OFFSET
   - No cargues todos los registros de una vez
   - Implementa paginación en consultas

===========================================
TROUBLESHOOTING
===========================================

ERROR: "Supabase no está instalado"
------------------------------------
Solución:
pip install supabase

ERROR: "Google GenAI no está instalado"
----------------------------------------
Solución:
pip install google-genai

ERROR: "No se encontró contexto relevante"
-------------------------------------------
Posibles causas:
1. El umbral de similitud es muy alto (0.7)
2. No hay documentos similares en la base de datos
3. Los embeddings no están generados correctamente

Solución:
- Reducir el umbral de similitud
- Verificar que la tabla tiene datos
- Regenerar embeddings con el mismo modelo (text-embedding-004)

ERROR: "type vector does not exist"
------------------------------------
Causa:
- La extensión pgvector no está habilitada en Supabase

Solución:
1. Ir al SQL Editor en Supabase Dashboard
2. Ejecutar: CREATE EXTENSION IF NOT EXISTS vector;
3. Verificar con: SELECT * FROM pg_extension WHERE extname = 'vector';
4. Luego crear la tabla con VECTOR(768)

ERROR: "función match_documents no existe"
-------------------------------------------
Solución:
- Crear la función RPC en Supabase (ver PASO 3)
- O modificar el código para usar tu función personalizada

ERROR: "GEMINI_API_KEY no está definida"
-----------------------------------------
Solución:
- Agregar GEMINI_API_KEY al archivo .env
- Verificar que el archivo .env está cargado
- Obtener API key en: https://aistudio.google.com/app/apikey

ERROR: "404 models/text-embedding-004 is not found for API version v1beta"
---------------------------------------------------------------------------
Causa:
- Versión desactualizada del framework (< v1.3.21)
- El código usa el nombre de modelo incorrecto (text-embedding-004)

Solución:
1. Actualizar el framework:
   pip install --upgrade haka-playwright-engine

2. Verificar que tienes google-genai (no google-generativeai):
   pip install --upgrade google-genai

3. Si el error persiste, verifica la versión:
   pip show haka-playwright-engine
   
   Debe ser >= 1.3.21

Nota técnica:
- Versiones < 1.3.19 usaban google-generativeai (deprecado)
- Versiones 1.3.19-1.3.20 usaban text-embedding-004 (nombre incorrecto)
- Versiones >= 1.3.21 usan gemini-embedding-001 (nombre correcto para la nueva API)
- El modelo correcto para google-genai es gemini-embedding-001

===========================================
MEJORES PRÁCTICAS
===========================================

1. CACHÉ DE EMBEDDINGS
   - Genera embeddings una sola vez y guárdalos
   - No regeneres embeddings en cada búsqueda

2. ÍNDICES VECTORIALES
   - Usa índices ivfflat o hnsw para búsquedas rápidas
   - Ajusta el número de listas según tu dataset

3. METADATA
   - Guarda metadata útil (fecha, autor, categoría)
   - Usa metadata para filtros adicionales

4. CHUNKING
   - Divide documentos largos en chunks de ~500 tokens
   - Mantén contexto entre chunks

5. TESTING
   - Prueba con diferentes queries
   - Valida que los documentos retornados son relevantes
   - Ajusta el umbral según tus necesidades

===========================================
RECURSOS ADICIONALES
===========================================

Documentación Supabase Vector:
https://supabase.com/docs/guides/ai/vector-columns

Documentación Gemini Embeddings:
https://ai.google.dev/gemini-api/docs/embeddings

Guía de RAG Testing:
Ver GUIA_TESTING_GEMINI_COMPLETA.txt

===========================================
VERSIÓN
===========================================

Funcionalidad disponible desde: haka-playwright-engine v1.3.17
Última actualización: Marzo 2026
Fix API Gemini embeddings: v1.3.19

===========================================
SOPORTE
===========================================

- GitHub Issues: https://github.com/pipefariashaka/haka-playwright-engine
- Documentación: hakalab_framework/documentacion/
- Email: felipe.farias@hakalab.com


===========================================
SCRIPT PYTHON: VECTORIZAR DOCUMENTOS
===========================================

DESCRIPCIÓN
-----------
Script para vectorizar documentos de texto y cargarlos en Supabase.
Divide documentos largos en chunks y genera embeddings con Gemini.

ARCHIVO: vectorize_documents.py
--------------------------------

```python
#!/usr/bin/env python3
"""
Script para vectorizar documentos de texto y cargarlos en Supabase
Usa Gemini para generar embeddings y los almacena en la base de datos vectorial

Uso:
    python vectorize_documents.py documento.txt
    python vectorize_documents.py carpeta_documentos/
    python vectorize_documents.py documento.txt --chunk-size 500 --overlap 50
"""

import os
import sys
import argparse
from pathlib import Path
from typing import List, Dict
import google.generativeai as genai
from supabase import create_client, Client
from dotenv import load_dotenv

# Cargar variables de entorno
load_dotenv()


def split_text_into_chunks(text: str, chunk_size: int = 500, overlap: int = 50) -> List[str]:
    """
    Divide el texto en chunks con overlap para mantener contexto
    
    Args:
        text: Texto a dividir
        chunk_size: Tamaño de cada chunk en palabras
        overlap: Número de palabras que se solapan entre chunks
    
    Returns:
        Lista de chunks de texto
    """
    words = text.split()
    chunks = []
    
    for i in range(0, len(words), chunk_size - overlap):
        chunk = ' '.join(words[i:i + chunk_size])
        if chunk.strip():
            chunks.append(chunk)
    
    return chunks


def generate_embedding(text: str, api_key: str) -> List[float]:
    """
    Genera embedding usando Gemini
    
    Args:
        text: Texto a vectorizar
        api_key: API key de Gemini
    
    Returns:
        Vector de embedding (768 dimensiones)
    """
    genai.configure(api_key=api_key)
    
    result = genai.embed_content(
        model="models/text-embedding-004",
        content=text,
        task_type="retrieval_document"  # Optimizado para documentos
    )
    
    return result['embedding']


def insert_into_supabase(
    supabase: Client,
    content: str,
    embedding: List[float],
    metadata: Dict
) -> None:
    """
    Inserta documento vectorizado en Supabase
    
    Args:
        supabase: Cliente de Supabase
        content: Contenido del documento
        embedding: Vector de embedding
        metadata: Metadata adicional (archivo, chunk_id, etc.)
    """
    data = {
        'content': content,
        'embedding': embedding,
        'metadata': metadata
    }
    
    result = supabase.table('knowledge_base').insert(data).execute()
    return result


def process_file(
    file_path: Path,
    supabase: Client,
    gemini_api_key: str,
    chunk_size: int = 500,
    overlap: int = 50
) -> int:
    """
    Procesa un archivo de texto y lo vectoriza en Supabase
    
    Args:
        file_path: Ruta al archivo
        supabase: Cliente de Supabase
        gemini_api_key: API key de Gemini
        chunk_size: Tamaño de chunks
        overlap: Overlap entre chunks
    
    Returns:
        Número de chunks procesados
    """
    print(f"\n📄 Procesando: {file_path.name}")
    
    # Leer archivo
    with open(file_path, 'r', encoding='utf-8') as f:
        text = f.read()
    
    # Dividir en chunks
    chunks = split_text_into_chunks(text, chunk_size, overlap)
    print(f"   ✂️  Dividido en {len(chunks)} chunks")
    
    # Procesar cada chunk
    for i, chunk in enumerate(chunks, 1):
        print(f"   🔄 Procesando chunk {i}/{len(chunks)}...", end=' ')
        
        # Generar embedding
        embedding = generate_embedding(chunk, gemini_api_key)
        
        # Metadata
        metadata = {
            'filename': file_path.name,
            'chunk_id': i,
            'total_chunks': len(chunks),
            'chunk_size': len(chunk.split()),
            'file_path': str(file_path)
        }
        
        # Insertar en Supabase
        insert_into_supabase(supabase, chunk, embedding, metadata)
        print("✅")
    
    print(f"   ✨ Completado: {len(chunks)} chunks vectorizados")
    return len(chunks)


def main():
    parser = argparse.ArgumentParser(
        description='Vectoriza documentos de texto y los carga en Supabase'
    )
    parser.add_argument(
        'path',
        help='Archivo .txt o carpeta con archivos .txt'
    )
    parser.add_argument(
        '--chunk-size',
        type=int,
        default=500,
        help='Tamaño de cada chunk en palabras (default: 500)'
    )
    parser.add_argument(
        '--overlap',
        type=int,
        default=50,
        help='Overlap entre chunks en palabras (default: 50)'
    )
    parser.add_argument(
        '--table',
        default='knowledge_base',
        help='Nombre de la tabla en Supabase (default: knowledge_base)'
    )
    
    args = parser.parse_args()
    
    # Verificar variables de entorno
    supabase_url = os.getenv('SUPABASE_URL')
    supabase_key = os.getenv('SUPABASE_KEY')
    gemini_api_key = os.getenv('GEMINI_API_KEY')
    
    if not all([supabase_url, supabase_key, gemini_api_key]):
        print("❌ Error: Variables de entorno faltantes")
        print("   Requeridas: SUPABASE_URL, SUPABASE_KEY, GEMINI_API_KEY")
        sys.exit(1)
    
    # Conectar a Supabase
    print("🔌 Conectando a Supabase...")
    supabase = create_client(supabase_url, supabase_key)
    print("✅ Conectado")
    
    # Procesar archivos
    path = Path(args.path)
    total_chunks = 0
    
    if path.is_file():
        # Procesar un solo archivo
        if path.suffix == '.txt':
            total_chunks = process_file(
                path,
                supabase,
                gemini_api_key,
                args.chunk_size,
                args.overlap
            )
        else:
            print(f"❌ Error: {path} no es un archivo .txt")
            sys.exit(1)
    
    elif path.is_dir():
        # Procesar todos los .txt en la carpeta
        txt_files = list(path.glob('*.txt'))
        
        if not txt_files:
            print(f"❌ Error: No se encontraron archivos .txt en {path}")
            sys.exit(1)
        
        print(f"\n📁 Encontrados {len(txt_files)} archivos .txt")
        
        for txt_file in txt_files:
            chunks = process_file(
                txt_file,
                supabase,
                gemini_api_key,
                args.chunk_size,
                args.overlap
            )
            total_chunks += chunks
    
    else:
        print(f"❌ Error: {path} no existe")
        sys.exit(1)
    
    # Resumen
    print("\n" + "="*50)
    print(f"✨ COMPLETADO")
    print(f"   Total de chunks vectorizados: {total_chunks}")
    print(f"   Tabla: {args.table}")
    print("="*50)


if __name__ == '__main__':
    main()
```

USO DEL SCRIPT
--------------

1. Guardar el script como `vectorize_documents.py`

2. Instalar dependencias:
   pip install supabase google-genai python-dotenv

3. Configurar .env:
   SUPABASE_URL=https://tu-proyecto.supabase.co
   SUPABASE_KEY=tu_supabase_key
   GEMINI_API_KEY=tu_gemini_api_key

4. Ejecutar:

   # Vectorizar un solo archivo
   python vectorize_documents.py documento.txt
   
   # Vectorizar todos los .txt de una carpeta
   python vectorize_documents.py carpeta_documentos/
   
   # Con parámetros personalizados
   python vectorize_documents.py documento.txt --chunk-size 300 --overlap 30
   
   # Especificar tabla diferente
   python vectorize_documents.py documento.txt --table mi_knowledge_base

PARÁMETROS
----------
--chunk-size: Tamaño de cada chunk en palabras (default: 500)
              Recomendado: 300-800 palabras
              
--overlap:    Palabras que se solapan entre chunks (default: 50)
              Recomendado: 10-15% del chunk-size
              
--table:      Nombre de la tabla en Supabase (default: knowledge_base)

EJEMPLOS DE USO
---------------

# Documentación técnica (chunks grandes)
python vectorize_documents.py docs/api_reference.txt --chunk-size 800 --overlap 100

# FAQs (chunks pequeños)
python vectorize_documents.py faqs/customer_support.txt --chunk-size 300 --overlap 30

# Procesar toda una carpeta de políticas
python vectorize_documents.py policies/ --chunk-size 500 --overlap 50

# Cargar en tabla personalizada
python vectorize_documents.py manual.txt --table product_manuals

VERIFICAR DATOS CARGADOS
-------------------------

Después de ejecutar el script, verifica en Supabase SQL Editor:

-- Ver total de documentos
SELECT COUNT(*) FROM knowledge_base;

-- Ver documentos por archivo
SELECT 
  metadata->>'filename' as archivo,
  COUNT(*) as chunks
FROM knowledge_base
GROUP BY metadata->>'filename';

-- Ver un documento de ejemplo
SELECT 
  id,
  LEFT(content, 100) as preview,
  metadata
FROM knowledge_base
LIMIT 5;

MEJORES PRÁCTICAS
-----------------

1. CHUNK SIZE
   - Documentos técnicos: 500-800 palabras
   - FAQs/Políticas: 300-500 palabras
   - Artículos largos: 600-1000 palabras

2. OVERLAP
   - Usar 10-15% del chunk_size
   - Mantiene contexto entre chunks
   - Mejora la calidad de búsqueda

3. METADATA
   - El script guarda automáticamente:
     * filename: Nombre del archivo
     * chunk_id: Número del chunk
     * total_chunks: Total de chunks del archivo
     * chunk_size: Palabras en el chunk
     * file_path: Ruta completa del archivo

4. ORGANIZACIÓN
   - Agrupa documentos por categoría en carpetas
   - Usa nombres descriptivos para archivos
   - Considera usar tablas diferentes por tipo de contenido

TROUBLESHOOTING
---------------

Error: "No module named 'google.generativeai'"
Solución: pip install google-genai

Error: "No module named 'supabase'"
Solución: pip install supabase

Error: "GEMINI_API_KEY not found"
Solución: Agregar GEMINI_API_KEY al archivo .env

Error: "relation knowledge_base does not exist"
Solución: Crear la tabla en Supabase (ver PASO 3)

Error: "column embedding does not exist"
Solución: Verificar que la tabla tiene la columna embedding VECTOR(768)
