===========================================
GUÍA DE STEPS SEMÁNTICOS PERSONALIZADOS
Framework Hakalab - Crear tus propios steps
===========================================

Esta guía te muestra cómo crear steps personalizados que usen la infraestructura
de validación semántica del framework.

===========================================
ÍNDICE
===========================================

1. Conceptos Básicos
2. Funciones Auxiliares Disponibles
3. Ejemplo 1: Step Simple de Validación
4. Ejemplo 2: Step con Múltiples Comparaciones
5. Ejemplo 3: Step con Lógica Personalizada
6. Ejemplo 4: Step con Extracción y Validación
7. Ejemplo 5: Step con Umbral Dinámico
8. Mejores Prácticas
9. Troubleshooting

===========================================
1. CONCEPTOS BÁSICOS
===========================================

Cuando creas un step personalizado que usa validación semántica, necesitas:

1. **Asegurar que el modelo esté cargado**: Usar `_ensure_semantic_config(context)`
2. **Acceder al modelo**: `context.semantic_model`
3. **Acceder al umbral**: `context.semantic_threshold`
4. **Calcular similitud**: Usar `_calculate_similarity(context, text1, text2)`
5. **Resolver variables**: Usar `context.variable_manager.resolve_variables(text)`

===========================================
2. FUNCIONES AUXILIARES DISPONIBLES
===========================================

El framework proporciona dos funciones auxiliares que puedes usar:

**_ensure_semantic_config(context)**
------------------------------------
Asegura que el modelo y umbral estén configurados. Si no lo están, carga la
configuración por defecto desde .env.

Uso:
```python
from hakalab_framework.steps.semantic_validation_steps import _ensure_semantic_config

def my_custom_step(context):
    _ensure_semantic_config(context)
    # Ahora puedes usar context.semantic_model y context.semantic_threshold
```

**_calculate_similarity(context, text1, text2)**
------------------------------------------------
Calcula la similitud de coseno entre dos textos usando el modelo configurado.

Retorna: float entre 0.0 (completamente diferentes) y 1.0 (idénticos)

Uso:
```python
from hakalab_framework.steps.semantic_validation_steps import _calculate_similarity

def my_custom_step(context, text1, text2):
    _ensure_semantic_config(context)
    similarity = _calculate_similarity(context, text1, text2)
    print(f"Similitud: {similarity:.4f}")
```

===========================================
3. EJEMPLO 1: STEP SIMPLE DE VALIDACIÓN
===========================================

Crear un step que valide si un texto es semánticamente similar a una lista
de opciones válidas.

**Archivo: hakalab_framework/steps/my_custom_steps.py**

```python
from behave import step, then
import logging
from hakalab_framework.steps.semantic_validation_steps import (
    _ensure_semantic_config,
    _calculate_similarity
)

logger = logging.getLogger(__name__)


@then('el texto "{actual_text}" debe ser similar a alguna de estas opciones "{valid_options}"')
def step_validate_text_against_options(context, actual_text, valid_options):
    """
    Valida que un texto sea semánticamente similar a al menos una de las opciones válidas.
    
    Ejemplo:
        Then el texto "Hola, buenos días" debe ser similar a alguna de estas opciones "Hola|Buenos días|Saludos"
    
    Args:
        actual_text: Texto a validar
        valid_options: Opciones válidas separadas por |
    """
    # Asegurar que el modelo esté configurado
    _ensure_semantic_config(context)
    
    # Resolver variables si existen
    actual_text = context.variable_manager.resolve_variables(actual_text)
    valid_options = context.variable_manager.resolve_variables(valid_options)
    
    # Separar opciones
    options = [opt.strip() for opt in valid_options.split('|')]
    
    # Calcular similitud con cada opción
    similarities = []
    for option in options:
        similarity = _calculate_similarity(context, actual_text, option)
        similarities.append((option, similarity))
        logger.info(f"   '{option}' → Similitud: {similarity:.4f}")
    
    # Encontrar la mejor coincidencia
    best_match = max(similarities, key=lambda x: x[1])
    best_option, best_similarity = best_match
    
    # Validar contra el umbral
    threshold = context.semantic_threshold
    
    if best_similarity >= threshold:
        logger.info(f"✅ Validación exitosa!")
        logger.info(f"   Mejor coincidencia: '{best_option}' ({best_similarity:.4f})")
        print(f"✅ Texto válido: '{actual_text}' es similar a '{best_option}' ({best_similarity:.4f})")
    else:
        raise AssertionError(
            f"❌ Ninguna opción alcanzó el umbral de similitud\n"
            f"Texto: {actual_text}\n"
            f"Mejor coincidencia: '{best_option}' ({best_similarity:.4f})\n"
            f"Umbral requerido: {threshold:.4f}\n"
            f"Opciones evaluadas: {', '.join(options)}"
        )
```

**Uso en Feature:**

```gherkin
Feature: Validar respuestas del chatbot

  Background:
    Given uso la configuración semántica por defecto

  Scenario: Validar saludo del chatbot
    When extraigo el texto del elemento "Saludo" y lo guardo en "saludo"
    Then el texto "${saludo}" debe ser similar a alguna de estas opciones "Hola|Buenos días|Bienvenido|Saludos"
```

===========================================
4. EJEMPLO 2: STEP CON MÚLTIPLES COMPARACIONES
===========================================

Crear un step que compare un texto con múltiples textos esperados y valide
que todos superen un umbral mínimo.

```python
@then('el texto "{actual_text}" debe ser similar a todos estos textos "{expected_texts}"')
def step_validate_text_against_all(context, actual_text, expected_texts):
    """
    Valida que un texto sea semánticamente similar a TODOS los textos esperados.
    
    Ejemplo:
        Then el texto "Pedido confirmado y enviado" debe ser similar a todos estos textos "Pedido confirmado|Pedido enviado"
    
    Args:
        actual_text: Texto a validar
        expected_texts: Textos esperados separados por |
    """
    _ensure_semantic_config(context)
    
    # Resolver variables
    actual_text = context.variable_manager.resolve_variables(actual_text)
    expected_texts = context.variable_manager.resolve_variables(expected_texts)
    
    # Separar textos esperados
    expected_list = [text.strip() for text in expected_texts.split('|')]
    
    # Validar contra cada texto
    threshold = context.semantic_threshold
    all_passed = True
    results = []
    
    for expected in expected_list:
        similarity = _calculate_similarity(context, actual_text, expected)
        passed = similarity >= threshold
        results.append((expected, similarity, passed))
        
        status = "✅" if passed else "❌"
        logger.info(f"{status} '{expected}' → Similitud: {similarity:.4f} (umbral: {threshold:.4f})")
        
        if not passed:
            all_passed = False
    
    if all_passed:
        print(f"✅ Todas las validaciones pasaron para: '{actual_text}'")
    else:
        failed = [r for r in results if not r[2]]
        raise AssertionError(
            f"❌ Algunas validaciones fallaron\n"
            f"Texto: {actual_text}\n"
            f"Fallaron: {', '.join([f'{r[0]} ({r[1]:.4f})' for r in failed])}\n"
            f"Umbral: {threshold:.4f}"
        )
```

**Uso en Feature:**

```gherkin
Scenario: Validar mensaje completo
  When extraigo el texto del elemento "Mensaje" y lo guardo en "mensaje"
  Then el texto "${mensaje}" debe ser similar a todos estos textos "Pedido confirmado|Envío procesado|Gracias por tu compra"
```

===========================================
5. EJEMPLO 3: STEP CON LÓGICA PERSONALIZADA
===========================================

Crear un step que use un umbral personalizado diferente al configurado.

```python
@then('el texto "{actual_text}" debe ser muy similar a "{expected_text}"')
def step_validate_high_similarity(context, actual_text, expected_text):
    """
    Valida que dos textos sean MUY similares (umbral 0.90).
    Ignora el umbral configurado y usa uno más estricto.
    
    Ejemplo:
        Then el texto "Hola" debe ser muy similar a "Hola, buenos días"
    
    Args:
        actual_text: Texto actual
        expected_text: Texto esperado
    """
    _ensure_semantic_config(context)
    
    # Resolver variables
    actual_text = context.variable_manager.resolve_variables(actual_text)
    expected_text = context.variable_manager.resolve_variables(expected_text)
    
    # Calcular similitud
    similarity = _calculate_similarity(context, actual_text, expected_text)
    
    # Usar umbral personalizado (muy estricto)
    custom_threshold = 0.90
    
    logger.info(f"🔍 Validación con umbral estricto:")
    logger.info(f"   Esperado: {expected_text}")
    logger.info(f"   Actual:   {actual_text}")
    logger.info(f"   Similitud: {similarity:.4f}")
    logger.info(f"   Umbral personalizado: {custom_threshold:.4f}")
    
    if similarity >= custom_threshold:
        print(f"✅ Textos muy similares: {similarity:.4f} >= {custom_threshold:.4f}")
    else:
        raise AssertionError(
            f"❌ Los textos no son suficientemente similares\n"
            f"Esperado: {expected_text}\n"
            f"Actual:   {actual_text}\n"
            f"Similitud: {similarity:.4f}\n"
            f"Umbral requerido: {custom_threshold:.4f} (muy estricto)"
        )


@then('el texto "{actual_text}" debe ser vagamente similar a "{expected_text}"')
def step_validate_low_similarity(context, actual_text, expected_text):
    """
    Valida que dos textos sean vagamente similares (umbral 0.60).
    Usa un umbral más permisivo que el configurado.
    
    Ejemplo:
        Then el texto "Error" debe ser vagamente similar a "Hubo un problema"
    
    Args:
        actual_text: Texto actual
        expected_text: Texto esperado
    """
    _ensure_semantic_config(context)
    
    # Resolver variables
    actual_text = context.variable_manager.resolve_variables(actual_text)
    expected_text = context.variable_manager.resolve_variables(expected_text)
    
    # Calcular similitud
    similarity = _calculate_similarity(context, actual_text, expected_text)
    
    # Usar umbral personalizado (permisivo)
    custom_threshold = 0.60
    
    logger.info(f"🔍 Validación con umbral permisivo:")
    logger.info(f"   Esperado: {expected_text}")
    logger.info(f"   Actual:   {actual_text}")
    logger.info(f"   Similitud: {similarity:.4f}")
    logger.info(f"   Umbral personalizado: {custom_threshold:.4f}")
    
    if similarity >= custom_threshold:
        print(f"✅ Textos vagamente similares: {similarity:.4f} >= {custom_threshold:.4f}")
    else:
        raise AssertionError(
            f"❌ Los textos no son ni siquiera vagamente similares\n"
            f"Esperado: {expected_text}\n"
            f"Actual:   {actual_text}\n"
            f"Similitud: {similarity:.4f}\n"
            f"Umbral requerido: {custom_threshold:.4f} (permisivo)"
        )
```

**Uso en Feature:**

```gherkin
Scenario: Validar con umbrales personalizados
  # Validación muy estricta (0.90)
  Then el texto "Hola" debe ser muy similar a "Hola, buenos días"
  
  # Validación permisiva (0.60)
  Then el texto "Error" debe ser vagamente similar a "Hubo un problema"
```

===========================================
6. EJEMPLO 4: STEP CON EXTRACCIÓN Y VALIDACIÓN
===========================================

Crear un step que extraiga texto de un elemento y lo valide en un solo paso.

```python
@then('el elemento "{element_name}" con identificador "{locator}" debe contener texto similar a "{expected_text}"')
def step_extract_and_validate_semantic(context, element_name, locator, expected_text):
    """
    Extrae texto de un elemento y valida que sea semánticamente similar al esperado.
    Todo en un solo paso.
    
    Ejemplo:
        Then el elemento "Mensaje" con identificador "$.CHAT.message" debe contener texto similar a "Hola, ¿cómo estás?"
    
    Args:
        element_name: Nombre descriptivo del elemento
        locator: Identificador del elemento
        expected_text: Texto esperado
    """
    _ensure_semantic_config(context)
    
    # Resolver variables
    expected_text = context.variable_manager.resolve_variables(expected_text)
    
    # Extraer texto del elemento
    from hakalab_framework.core.element_locator import ElementLocator
    
    element_locator = ElementLocator(context)
    element = element_locator.find_element(locator)
    
    if not element:
        raise AssertionError(f"❌ No se encontró el elemento: {element_name} ({locator})")
    
    actual_text = element.inner_text().strip()
    logger.info(f"📝 Texto extraído de '{element_name}': {actual_text}")
    
    # Calcular similitud
    similarity = _calculate_similarity(context, actual_text, expected_text)
    threshold = context.semantic_threshold
    
    logger.info(f"🔍 Validación semántica:")
    logger.info(f"   Esperado: {expected_text}")
    logger.info(f"   Actual:   {actual_text}")
    logger.info(f"   Similitud: {similarity:.4f}")
    logger.info(f"   Umbral: {threshold:.4f}")
    
    if similarity >= threshold:
        print(f"✅ Elemento '{element_name}' contiene texto similar: {similarity:.4f}")
    else:
        raise AssertionError(
            f"❌ El texto del elemento no es semánticamente similar\n"
            f"Elemento: {element_name}\n"
            f"Esperado: {expected_text}\n"
            f"Actual:   {actual_text}\n"
            f"Similitud: {similarity:.4f}\n"
            f"Umbral: {threshold:.4f}"
        )
```

**Uso en Feature:**

```gherkin
Scenario: Validar elemento directamente
  Given navego a "https://mi-app.com"
  And uso la configuración semántica por defecto
  Then el elemento "Mensaje de bienvenida" con identificador "$.HOME.welcome_message" debe contener texto similar a "Bienvenido a nuestra aplicación"
```

===========================================
7. EJEMPLO 5: STEP CON UMBRAL DINÁMICO
===========================================

Crear un step que permita especificar el umbral en el mismo paso.

```python
@then('el texto "{actual_text}" debe ser similar a "{expected_text}" con umbral {custom_threshold:f}')
def step_validate_with_custom_threshold(context, actual_text, expected_text, custom_threshold):
    """
    Valida similitud semántica usando un umbral personalizado especificado en el paso.
    
    Ejemplo:
        Then el texto "Hola" debe ser similar a "Buenos días" con umbral 0.65
    
    Args:
        actual_text: Texto actual
        expected_text: Texto esperado
        custom_threshold: Umbral personalizado (0.0 - 1.0)
    """
    _ensure_semantic_config(context)
    
    # Validar umbral
    if not 0.0 <= custom_threshold <= 1.0:
        raise ValueError(f"El umbral debe estar entre 0.0 y 1.0, recibido: {custom_threshold}")
    
    # Resolver variables
    actual_text = context.variable_manager.resolve_variables(actual_text)
    expected_text = context.variable_manager.resolve_variables(expected_text)
    
    # Calcular similitud
    similarity = _calculate_similarity(context, actual_text, expected_text)
    
    logger.info(f"🔍 Validación con umbral personalizado:")
    logger.info(f"   Esperado: {expected_text}")
    logger.info(f"   Actual:   {actual_text}")
    logger.info(f"   Similitud: {similarity:.4f}")
    logger.info(f"   Umbral personalizado: {custom_threshold:.4f}")
    logger.info(f"   Umbral configurado: {context.semantic_threshold:.4f}")
    
    if similarity >= custom_threshold:
        print(f"✅ Validación exitosa con umbral {custom_threshold:.4f}: {similarity:.4f}")
    else:
        raise AssertionError(
            f"❌ Similitud insuficiente\n"
            f"Esperado: {expected_text}\n"
            f"Actual:   {actual_text}\n"
            f"Similitud: {similarity:.4f}\n"
            f"Umbral requerido: {custom_threshold:.4f}"
        )
```

**Uso en Feature:**

```gherkin
Scenario: Validar con diferentes umbrales
  Given uso la configuración semántica por defecto
  
  # Umbral bajo (permisivo)
  Then el texto "Error" debe ser similar a "Problema" con umbral 0.60
  
  # Umbral medio (estándar)
  Then el texto "Pedido confirmado" debe ser similar a "Tu pedido fue confirmado" con umbral 0.75
  
  # Umbral alto (estricto)
  Then el texto "Hola" debe ser similar a "Hola, buenos días" con umbral 0.90
```

===========================================
8. MEJORES PRÁCTICAS
===========================================

1. SIEMPRE USAR _ensure_semantic_config
---------------------------------------
✅ Llama a `_ensure_semantic_config(context)` al inicio de tu step
✅ Esto asegura que el modelo esté cargado antes de usarlo
✅ Si no está configurado, carga automáticamente desde .env

2. RESOLVER VARIABLES
----------------------
✅ Usa `context.variable_manager.resolve_variables(text)` para resolver ${variables}
✅ Hazlo ANTES de calcular similitudes
✅ Aplica a todos los textos que recibas como parámetros

3. LOGGING INFORMATIVO
-----------------------
✅ Usa logger.info() para mostrar información de debug
✅ Muestra los textos comparados, similitud y umbral
✅ Ayuda a diagnosticar problemas

4. MENSAJES DE ERROR CLAROS
----------------------------
✅ Incluye en el AssertionError:
   - Textos comparados
   - Similitud obtenida
   - Umbral requerido
   - Contexto adicional relevante

5. VALIDAR PARÁMETROS
----------------------
✅ Valida que los umbrales estén entre 0.0 y 1.0
✅ Valida que los textos no estén vacíos
✅ Maneja casos edge (None, strings vacíos, etc.)

6. DOCUMENTACIÓN
----------------
✅ Documenta tu step con docstring
✅ Incluye ejemplos de uso
✅ Explica qué hace y cuándo usarlo

===========================================
9. TROUBLESHOOTING
===========================================

Error: "AttributeError: 'Context' object has no attribute 'semantic_model'"
---------------------------------------------------------------------------
Solución: Olvidaste llamar a `_ensure_semantic_config(context)`

```python
# ❌ INCORRECTO
def my_step(context):
    similarity = _calculate_similarity(context, text1, text2)  # Error!

# ✅ CORRECTO
def my_step(context):
    _ensure_semantic_config(context)  # Primero asegurar configuración
    similarity = _calculate_similarity(context, text1, text2)
```

Error: "Variables no se resuelven"
----------------------------------
Solución: Usa `context.variable_manager.resolve_variables()`

```python
# ❌ INCORRECTO
def my_step(context, text):
    similarity = _calculate_similarity(context, text, "esperado")  # ${var} no se resuelve

# ✅ CORRECTO
def my_step(context, text):
    _ensure_semantic_config(context)
    text = context.variable_manager.resolve_variables(text)  # Resolver ${var}
    similarity = _calculate_similarity(context, text, "esperado")
```

Error: "ImportError: cannot import name '_ensure_semantic_config'"
------------------------------------------------------------------
Solución: Importa correctamente desde el módulo

```python
# ✅ CORRECTO
from hakalab_framework.steps.semantic_validation_steps import (
    _ensure_semantic_config,
    _calculate_similarity
)
```

Error: "Similitud siempre es 1.0"
----------------------------------
Solución: Estás comparando el mismo texto consigo mismo

```python
# ❌ INCORRECTO
similarity = _calculate_similarity(context, text, text)  # Siempre 1.0

# ✅ CORRECTO
similarity = _calculate_similarity(context, actual_text, expected_text)
```

===========================================
EJEMPLO COMPLETO DE ARCHIVO PERSONALIZADO
===========================================

**Archivo: hakalab_framework/steps/my_semantic_steps.py**

```python
#!/usr/bin/env python3
"""
Steps personalizados para validación semántica
"""
from behave import step, then
import logging
from hakalab_framework.steps.semantic_validation_steps import (
    _ensure_semantic_config,
    _calculate_similarity
)

logger = logging.getLogger(__name__)


@then('el texto "{actual_text}" debe ser similar a alguna de estas opciones "{valid_options}"')
def step_validate_text_against_options(context, actual_text, valid_options):
    """Valida que un texto sea similar a al menos una opción"""
    _ensure_semantic_config(context)
    
    actual_text = context.variable_manager.resolve_variables(actual_text)
    valid_options = context.variable_manager.resolve_variables(valid_options)
    
    options = [opt.strip() for opt in valid_options.split('|')]
    similarities = []
    
    for option in options:
        similarity = _calculate_similarity(context, actual_text, option)
        similarities.append((option, similarity))
        logger.info(f"   '{option}' → {similarity:.4f}")
    
    best_match = max(similarities, key=lambda x: x[1])
    best_option, best_similarity = best_match
    threshold = context.semantic_threshold
    
    if best_similarity >= threshold:
        print(f"✅ '{actual_text}' es similar a '{best_option}' ({best_similarity:.4f})")
    else:
        raise AssertionError(
            f"❌ Ninguna opción alcanzó el umbral\n"
            f"Mejor: '{best_option}' ({best_similarity:.4f}) < {threshold:.4f}"
        )


@then('el texto "{actual_text}" debe ser muy similar a "{expected_text}"')
def step_validate_high_similarity(context, actual_text, expected_text):
    """Valida con umbral estricto (0.90)"""
    _ensure_semantic_config(context)
    
    actual_text = context.variable_manager.resolve_variables(actual_text)
    expected_text = context.variable_manager.resolve_variables(expected_text)
    
    similarity = _calculate_similarity(context, actual_text, expected_text)
    custom_threshold = 0.90
    
    if similarity >= custom_threshold:
        print(f"✅ Muy similares: {similarity:.4f}")
    else:
        raise AssertionError(
            f"❌ No suficientemente similares: {similarity:.4f} < {custom_threshold:.4f}"
        )
```

**Uso en Feature:**

```gherkin
Feature: Testing con steps personalizados

  Background:
    Given navego a "https://mi-app.com"
    And uso la configuración semántica por defecto

  Scenario: Validar con opciones múltiples
    When extraigo el texto del elemento "Saludo" y lo guardo en "saludo"
    Then el texto "${saludo}" debe ser similar a alguna de estas opciones "Hola|Buenos días|Bienvenido"

  Scenario: Validar con umbral estricto
    When extraigo el texto del elemento "Título" y lo guardo en "titulo"
    Then el texto "${titulo}" debe ser muy similar a "Bienvenido a nuestra aplicación"
```

===========================================
FIN DE LA GUÍA
===========================================

Versión: 1.3.0
Última actualización: 2026-02-13

Para más información:
- GUIA_VALIDACION_SEMANTICA_IA.txt (guía completa)
- INICIO_RAPIDO_VALIDACION_SEMANTICA.txt (inicio rápido)
- semantic_validation_steps.py (código fuente)
