|
import os |
|
import gradio as gr |
|
from gradio import ChatMessage |
|
import torch |
|
import torch._dynamo |
|
from transformers import AutoModelForCausalLM, AutoTokenizer |
|
from huggingface_hub import hf_hub_download |
|
import re |
|
from llama_cpp import Llama |
|
from typing import Iterator |
|
import spaces |
|
|
|
|
|
torch._dynamo.config.suppress_errors = True |
|
torch._dynamo.disable() |
|
|
|
|
|
MODEL_ID = "somosnlp-hackathon-2025/iberotales-gemma-3-1b-it-es" |
|
GGUF_MODEL_ID = "somosnlp-hackathon-2025/iberotales-gemma-3-1b-it-es-finetune-gguf" |
|
GGUF_FILENAME = "gemma-3-finetune.Q8_0.gguf" |
|
GGUF_REVISION = "main" |
|
MAX_MAX_NEW_TOKENS = 2048 |
|
DEFAULT_MAX_NEW_TOKENS = 1024 |
|
|
|
|
|
DEFAULT_SYSTEM_MESSAGE = """Resuelve el siguiente problema. |
|
Primero, piensa en voz alta qué debes hacer, paso por paso y de forma resumida, entre <think> y </think>. |
|
Luego, da la respuesta final entre <SOLUTION> y </SOLUTION>. |
|
No escribas nada fuera de ese formato.""" |
|
|
|
|
|
PERSONAJES_POR_PAIS = { |
|
"🇦🇷 Argentina": [ |
|
{"nombre": "La Difunta Correa", "imagen": "images/ar1.jpg", "descripcion": "Santa popular que murió de sed siguiendo a su esposo reclutado"}, |
|
{"nombre": "El Lobizón", "imagen": "images/ar2.jpg", "descripcion": "Hombre lobo de la tradición gaucha, séptimo hijo varón maldito"}, |
|
{"nombre": "La Telesita", "imagen": "images/ar3.webp", "descripcion": "Bailarina folklórica que se aparece en festivales y zambas"} |
|
], |
|
"🇧🇴 Bolivia": [ |
|
{"nombre": "El Tío del Cerro Rico", "imagen": "images/bo1.webp", "descripcion": "Señor de las minas que protege y castiga a los mineros"}, |
|
{"nombre": "El Ekeko", "imagen": "images/bo2.jpg", "descripcion": "Dios aymara de la abundancia y la fortuna con jorobas"}, |
|
{"nombre": "El Jichi", "imagen": "images/bo3.webp", "descripcion": "Serpiente protectora de ríos y lagunas en la cultura andina"} |
|
], |
|
"🇧🇷 Brasil": [ |
|
{"nombre": "Curupira", "imagen": "images/br1.jpeg", "descripcion": "Protector del bosque amazónico con pies al revés"}, |
|
{"nombre": "Saci-Pererê", "imagen": "images/br2.jpg", "descripcion": "Duende travieso de una pierna que fuma pipa"}, |
|
{"nombre": "Yebá Bëló", "imagen": "images/br3.jpg", "descripcion": "Abuela del mundo en mitología desana, creadora del universo"} |
|
], |
|
"🇨🇱 Chile": [ |
|
{"nombre": "Guallipén", "imagen": "images/ch1.webp", "descripcion": "Carnero gigante que habita en los ríos de Chiloé"}, |
|
{"nombre": "Colo Colo", "imagen": "images/ch2.webp", "descripcion": "Ser maléfico nacido de huevo de gallo empollado por serpiente"}, |
|
{"nombre": "Cuchivilu", "imagen": "images/ch3.webp", "descripcion": "Cerdo marino con cola de pez que habita en Chiloé"} |
|
], |
|
"🇨🇴 Colombia": [ |
|
{"nombre": "El Guaca", "imagen": "images/co1.jpg", "descripcion": "Espíritu guardián de tesoros enterrados por indígenas"}, |
|
{"nombre": "Chiminigagua", "imagen": "images/co2.webp", "descripcion": "Dios creador muisca, fuente de luz y vida universal"}, |
|
{"nombre": "Jukumari", "imagen": "images/co3.jpg", "descripcion": "Oso gigante mitológico de los Andes colombianos"} |
|
], |
|
"🇨🇷 Costa Rica": [ |
|
{"nombre": "El Micomalo", "imagen": "images/cr1.jpg", "descripcion": "Mono gigante y feroz que ataca a los cazadores"}, |
|
{"nombre": "La Carreta sin Bueyes", "imagen": "images/crr2.jpg", "descripcion": "Carreta fantasma que recorre caminos sin animales tirando"}, |
|
{"nombre": "El Sisimiqui", "imagen": "images/cr3.webp", "descripcion": "Duende pequeño y travieso de los bosques costarricenses"} |
|
], |
|
"🇨🇺 Cuba": [ |
|
{"nombre": "El Güije", "imagen": "images/cu1.jpg", "descripcion": "Duende negro de aguas dulces que ahoga a los bañistas"}, |
|
{"nombre": "Itiba Cahubaba", "imagen": "images/cu2.jpg", "descripcion": "Madre primordial taína de donde brotaron las aguas"}, |
|
{"nombre": "Guabancex", "imagen": "images/cu3.jpg", "descripcion": "Diosa taína de los huracanes y vientos destructores"} |
|
], |
|
"🇪🇨 Ecuador": [ |
|
{"nombre": "Salun", "imagen": "images/ec1.jpg", "descripcion": "Espíritu shamán shuar que guía en visiones espirituales"}, |
|
{"nombre": "Nunkui", "imagen": "images/ec2.jpg", "descripcion": "Diosa shuar de la fertilidad de la tierra y agricultura"}, |
|
{"nombre": "Yacuruna", "imagen": "images/ec3.jpg", "descripcion": "Hombre serpiente amazónico señor de las aguas dulces"} |
|
], |
|
"🇪🇸 España": [ |
|
{"nombre": "Ojáncanu", "imagen": "images/es1.jpg", "descripcion": "Gigante cíclope cántabro de fuerza descomunal"}, |
|
{"nombre": "Los Carantos", "imagen": "images/es2.jpg", "descripcion": "Espíritus gallegos que anuncian desgracias familiares"}, |
|
{"nombre": "Cuegle", "imagen": "images/es3.jpg", "descripcion": "Ser asturiano de tres ojos que protege casas del mal"} |
|
], |
|
"🇬🇹 Guatemala": [ |
|
{"nombre": "Camalotz", "imagen": "images/gu1.jpg", "descripcion": "Dios murciélago maya señor de las cuevas y la muerte"}, |
|
{"nombre": "La Siguanaba", "imagen": "images/gu2.jpg", "descripcion": "Mujer hermosa que muestra rostro de calavera a infieles"}, |
|
{"nombre": "Gucumatz", "imagen": "images/gu3.jpg", "descripcion": "Serpiente emplumada maya, equivalente a Quetzalcóatl"} |
|
], |
|
"🇭🇳 Honduras": [ |
|
{"nombre": "Icelaca", "imagen": "images/ho1.jpg", "descripcion": "Serpiente gigante que habita en cuevas hondureñas"}, |
|
{"nombre": "Cihuateteo", "imagen": "images/ho2.jpg", "descripcion": "Espíritus de mujeres muertas en parto, guerreras divinas"}, |
|
{"nombre": "Comelenguas", "imagen": "images/ho3.webp", "descripcion": "Demonio que devora lenguas de personas dormidas"} |
|
], |
|
"🇲🇽 México": [ |
|
{"nombre": "Quetzalcóatl", "imagen": "images/me1.jpg", "descripcion": "Serpiente emplumada, dios del viento y la sabiduría"}, |
|
{"nombre": "Tlacatecolotl", "imagen": "images/me2.jpg", "descripcion": "Brujo nahuatl que se transforma en búho gigante"}, |
|
{"nombre": "Tlalocan", "imagen": "images/me3.jpg", "descripcion": "Paraíso acuático de Tláloc donde van los ahogados"} |
|
], |
|
"🇳🇮 Nicaragua": [ |
|
{"nombre": "La Mocuana", "imagen": "images/ni1.webp", "descripcion": "Espíritu de mujer sin cabeza que vaga de noche"}, |
|
{"nombre": "El Guácimo Renco", "imagen": "images/ni2.jpg", "descripcion": "Árbol maldito que se transforma y persigue viajeros"}, |
|
{"nombre": "La Mona Bruja", "imagen": "images/ni3.jpg", "descripcion": "Bruja transformada en mona que roba niños pequeños"} |
|
], |
|
"🇵🇦 Panamá": [ |
|
{"nombre": "Humantahú", "imagen": "images/pa1.jpg", "descripcion": "Chamán kuna protector de la naturaleza y animales"}, |
|
{"nombre": "La Silampa", "imagen": "images/pa2.jpg", "descripcion": "Mujer fantasma que seduce y mata a hombres solitarios"}, |
|
{"nombre": "El Chivato", "imagen": "images/pa3.jpg", "descripcion": "Cabro diabólico que aparece en encrucijadas nocturnas"} |
|
], |
|
"🇵🇾 Paraguay": [ |
|
{"nombre": "Mbói Tata", "imagen": "images/py1.png", "descripcion": "Serpiente de fuego protectora de campos y esteros"}, |
|
{"nombre": "Urutau", "imagen": "images/py2.jpg", "descripcion": "Ave fantasma cuyo canto anuncia tragedias familiares"}, |
|
{"nombre": "Tajy", "imagen": "images/py3.jpg", "descripcion": "Espíritu del lapacho florido en la mitología guaraní"} |
|
], |
|
"🇵🇪 Perú": [ |
|
{"nombre": "Wiracocha", "imagen": "images/pe1.jpg", "descripcion": "Dios creador inca señor de todas las cosas vivientes"}, |
|
{"nombre": "El Muki", "imagen": "images/pe2.webp", "descripcion": "Duende minero que protege vetas de oro y plata"}, |
|
{"nombre": "Los Hermanos Ayar", "imagen": "images/pe3.jpg", "descripcion": "Cuatro hermanos fundadores míticos del imperio inca"} |
|
], |
|
"🇵🇹 Portugal": [ |
|
{"nombre": "El Marialva", "imagen": "images/po1.jpg", "descripcion": "Caballero galante y seductor de la tradición portuguesa"}, |
|
{"nombre": "El Pez Milagroso", "imagen": "images/po2.webp", "descripcion": "Pez mágico que concede deseos a pescadores devotos"}, |
|
{"nombre": "La Cueva da Moeda", "imagen": "images/po3.webp", "descripcion": "Cueva encantada llena de tesoros moros perdidos"} |
|
], |
|
"🇵🇷 Puerto Rico": [ |
|
{"nombre": "Yúcahu", "imagen": "images/pr1.jpg", "descripcion": "Dios taíno de la yuca y protector de cosechas"}, |
|
{"nombre": "Atabey", "imagen": "images/pr2.jpg", "descripcion": "Diosa madre taína de las aguas dulces y fertilidad"}, |
|
{"nombre": "Anacacuya", "imagen": "images/pr3.png", "descripcion": "Estrella taína guía de navegantes y pescadores"} |
|
], |
|
"🇩🇴 República Dominicana": [ |
|
{"nombre": "El Galipote", "imagen": "images/rd1.webp", "descripcion": "Brujo que se transforma en animal para hacer maldades"}, |
|
{"nombre": "El Lugaru", "imagen": "images/rd2.webp", "descripcion": "Hombre lobo dominicano que ataca ganado y personas"}, |
|
{"nombre": "Papa Legba", "imagen": "images/rd3.jpg", "descripcion": "Loa vudú guardián de encrucijadas y comunicaciones"} |
|
], |
|
"🇺🇾 Uruguay": [ |
|
{"nombre": "El Yasy Yateré", "imagen": "images/ur1.jpg", "descripcion": "Duende rubio protector de niños con bastón mágico"}, |
|
{"nombre": "El Ñandú Barriga Blanca", "imagen": "images/ur2.jpg", "descripcion": "Ñandú gigante y sagrado de las pampas uruguayas"}, |
|
{"nombre": "El Pombero", "imagen": "images/ur3.jpg", "descripcion": "Duende travieso protector de aves y la naturaleza"} |
|
], |
|
"🇻🇪 Venezuela": [ |
|
{"nombre": "Amalivaca", "imagen": "images/ve1.jpg", "descripcion": "Héroe cultural tamanaco creador del río Orinoco"}, |
|
{"nombre": "Osemma", "imagen": "images/ve2.jpg", "descripcion": "Espíritu warao de las palmeras de moriche"}, |
|
{"nombre": "La Sayona", "imagen": "images/ve3.webp", "descripcion": "Mujer vengativa que persigue y castiga a infieles"} |
|
] |
|
}; |
|
|
|
|
|
model = None |
|
tokenizer = None |
|
current_personajes = [] |
|
|
|
|
|
custom_css = """ |
|
.gradio-container { |
|
max-width: 1400px !important; |
|
margin: auto; |
|
padding-top: 1.5rem; |
|
} |
|
|
|
#galeria .grid-wrap { |
|
max-height: 350px; |
|
overflow-y: auto; |
|
} |
|
|
|
#galeria .grid-container { |
|
grid-template-columns: repeat(1, 1fr) !important; |
|
gap: 0.5rem; |
|
} |
|
|
|
#galeria .thumbnail-item { |
|
aspect-ratio: 1; |
|
max-height: 100px; |
|
} |
|
|
|
#galeria .thumbnail-item img { |
|
object-fit: cover; |
|
width: 100%; |
|
height: 100%; |
|
border-radius: 8px; |
|
} |
|
|
|
.header-info { |
|
background: linear-gradient(135deg, #2c3e50 0%, #1a1a2e 100%); |
|
color: white; |
|
padding: 1rem; |
|
border-radius: 12px; |
|
margin-bottom: 1rem; |
|
text-align: center; |
|
} |
|
""" |
|
|
|
def load_model(): |
|
"""Cargar modelo GGUF""" |
|
global model, tokenizer |
|
|
|
try: |
|
|
|
print("Descargando modelo GGUF...") |
|
model_path = hf_hub_download( |
|
repo_id=GGUF_MODEL_ID, |
|
filename=GGUF_FILENAME, |
|
revision=GGUF_REVISION, |
|
local_dir="./models", |
|
) |
|
|
|
print(f"Modelo descargado en: {model_path}") |
|
|
|
|
|
model = Llama( |
|
model_path=model_path, |
|
n_ctx=2048, |
|
n_batch=512, |
|
n_threads=4, |
|
n_gpu_layers=0, |
|
chat_format="gemma" |
|
) |
|
|
|
|
|
tokenizer = AutoTokenizer.from_pretrained("google/gemma-2-2b-it") |
|
|
|
print("Modelo cargado exitosamente") |
|
return True |
|
|
|
except Exception as e: |
|
print(f"Error al cargar modelo: {e}") |
|
return False |
|
|
|
def generate_response( |
|
user_message: str, |
|
system_message: str = DEFAULT_SYSTEM_MESSAGE, |
|
max_new_tokens: int = DEFAULT_MAX_NEW_TOKENS, |
|
temperature: float = 0.7, |
|
top_p: float = 0.95, |
|
): |
|
"""Genera respuesta usando el modelo GGUF""" |
|
global model |
|
|
|
if model is None: |
|
return "Error: Modelo no disponible." |
|
|
|
try: |
|
|
|
messages = [ |
|
{"role": "system", "content": system_message}, |
|
{"role": "user", "content": user_message} |
|
] |
|
|
|
|
|
response = model.create_chat_completion( |
|
messages=messages, |
|
max_tokens=max_new_tokens, |
|
temperature=temperature, |
|
top_p=top_p, |
|
stream=False |
|
) |
|
|
|
full_response = response['choices'][0]['message']['content'] |
|
|
|
|
|
thinking_part = "" |
|
solution_part = "" |
|
|
|
|
|
think_match = re.search(r'<think>(.*?)</think>', full_response, re.DOTALL) |
|
if think_match: |
|
thinking_part = think_match.group(1).strip() |
|
|
|
|
|
solution_match = re.search(r'<SOLUTION>(.*?)</SOLUTION>', full_response, re.DOTALL) |
|
if solution_match: |
|
solution_part = solution_match.group(1).strip() |
|
|
|
|
|
messages = [] |
|
|
|
if thinking_part: |
|
messages.append(ChatMessage( |
|
role="assistant", |
|
content=thinking_part, |
|
metadata={"title": "🤔 Pensando..."} |
|
)) |
|
|
|
if solution_part: |
|
messages.append(ChatMessage( |
|
role="assistant", |
|
content=solution_part |
|
)) |
|
elif not thinking_part: |
|
|
|
clean_response = re.sub(r'<think>.*?</think>', '', full_response, flags=re.DOTALL) |
|
clean_response = re.sub(r'<SOLUTION>(.*?)</SOLUTION>', r'\1', clean_response, flags=re.DOTALL) |
|
messages.append(ChatMessage( |
|
role="assistant", |
|
content=clean_response.strip() |
|
)) |
|
|
|
return messages |
|
|
|
except Exception as e: |
|
return [ChatMessage(role="assistant", content=f"Error: {str(e)}")] |
|
|
|
def crear_imagen_placeholder(): |
|
"""Crea una imagen placeholder simple usando PIL""" |
|
try: |
|
from PIL import Image, ImageDraw, ImageFont |
|
import io |
|
import base64 |
|
|
|
|
|
img = Image.new('RGB', (100, 100), color='lightgray') |
|
draw = ImageDraw.Draw(img) |
|
|
|
|
|
draw.text((10, 40), "No Image", fill='black') |
|
|
|
|
|
buffer = io.BytesIO() |
|
img.save(buffer, format='PNG') |
|
img_str = base64.b64encode(buffer.getvalue()).decode() |
|
|
|
return f"data:image/png;base64,{img_str}" |
|
except: |
|
|
|
return None |
|
|
|
def actualizar_personajes(pais_seleccionado): |
|
"""Actualiza la galería de personajes según el país seleccionado""" |
|
global current_personajes |
|
|
|
if not pais_seleccionado: |
|
return gr.Gallery(value=None, visible=False), "Selecciona un país para ver sus personajes" |
|
|
|
personajes = PERSONAJES_POR_PAIS.get(pais_seleccionado, []) |
|
current_personajes = personajes |
|
|
|
if not personajes: |
|
return gr.Gallery(value=None, visible=False), "No hay personajes disponibles para este país" |
|
|
|
|
|
imagenes_validas = [] |
|
|
|
for i, personaje in enumerate(personajes): |
|
imagen_path = personaje["imagen"] |
|
|
|
|
|
if os.path.exists(imagen_path) and os.path.isfile(imagen_path): |
|
try: |
|
|
|
from PIL import Image |
|
with Image.open(imagen_path) as img: |
|
img.verify() |
|
imagenes_validas.append(imagen_path) |
|
except Exception as e: |
|
print(f"Error al verificar imagen {imagen_path}: {e}") |
|
|
|
continue |
|
else: |
|
print(f"Imagen no encontrada: {imagen_path}") |
|
|
|
continue |
|
|
|
|
|
if not imagenes_validas: |
|
return gr.Gallery( |
|
value=None, |
|
visible=True, |
|
label=f"⚠️ Imágenes no encontradas para {pais_seleccionado}" |
|
), f"Las imágenes para {pais_seleccionado} no están disponibles" |
|
|
|
|
|
return gr.Gallery( |
|
value=imagenes_validas, |
|
visible=True, |
|
label=f"Personajes de {pais_seleccionado}" |
|
), f"Mostrando {len(imagenes_validas)} personajes de {pais_seleccionado}" |
|
|
|
def crear_prompt_desde_personaje(evt: gr.SelectData): |
|
"""Crea un prompt basado en el personaje seleccionado""" |
|
global current_personajes |
|
|
|
try: |
|
if evt.index is not None and 0 <= evt.index < len(current_personajes): |
|
personaje = current_personajes[evt.index] |
|
prompt = f"Crea una historia sobre {personaje['nombre']}: {personaje['descripcion']}" |
|
return prompt |
|
else: |
|
return "Crea una historia sobre un personaje mítico" |
|
except Exception as e: |
|
print(f"Error al crear prompt: {e}") |
|
return "Crea una historia sobre un personaje mítico" |
|
|
|
def chat_function(message, history, max_tokens, temperature): |
|
"""Función principal del chat""" |
|
if not message.strip(): |
|
return history, "" |
|
|
|
|
|
history = history + [ChatMessage(role="user", content=message)] |
|
|
|
|
|
response_messages = generate_response(message, DEFAULT_SYSTEM_MESSAGE, max_tokens, temperature) |
|
|
|
|
|
for msg in response_messages: |
|
history = history + [msg] |
|
|
|
return history, "" |
|
|
|
|
|
print("Iniciando carga del modelo...") |
|
model_loaded = load_model() |
|
|
|
|
|
with gr.Blocks(title="Iberotales", css=custom_css) as demo: |
|
|
|
gr.HTML(""" |
|
<div class="header-info"> |
|
<h1>📚 Iberotales</h1> |
|
<p><strong>Autor:</strong> David Quispe | <a href="https://github.com/mcdaqc/Iberotales" target="_blank" style="text-decoration: none;">GitHub</a> | <a href="https://huggingface.co/somosnlp-hackathon-2025/iberotales-gemma-3-1b-it-es" target="_blank" style="text-decoration: none;">Modelo</a> | <a href="https://huggingface.co/somosnlp-hackathon-2025/iberotales-gemma-3-1b-it-es-finetune-gguf" target="_blank" style="text-decoration: none;">GGUF</a></p> |
|
<p><em>Alineando modelos de lenguaje con la narrativa de mitos y leyendas de Iberoamérica.</em></p> |
|
<p><em>Hackathon SomosNLP 2025</em></p> |
|
</div> |
|
""") |
|
|
|
with gr.Row(): |
|
|
|
with gr.Column(scale=1, min_width=320): |
|
gr.Markdown("### 🗃️ Pokédex de Personajes") |
|
|
|
pais_dropdown = gr.Dropdown( |
|
choices=list(PERSONAJES_POR_PAIS.keys()), |
|
value="🇵🇾 Paraguay", |
|
label="País", |
|
container=False |
|
) |
|
|
|
|
|
galeria_personajes = gr.Gallery( |
|
value=None, |
|
label="Selecciona un país", |
|
show_label=True, |
|
elem_id="galeria", |
|
columns=1, |
|
rows=4, |
|
height=350, |
|
type="filepath" |
|
) |
|
|
|
|
|
status_msg = gr.Textbox( |
|
value="Selecciona un país para ver sus personajes", |
|
show_label=False, |
|
interactive=False, |
|
container=False |
|
) |
|
|
|
|
|
with gr.Column(scale=2): |
|
chatbot = gr.Chatbot( |
|
type="messages", |
|
show_label=False, |
|
height=400, |
|
avatar_images=(None, "🏛️"), |
|
value=[] |
|
) |
|
|
|
with gr.Row(): |
|
input_box = gr.Textbox( |
|
placeholder="Escribe tu historia o selecciona un personaje...", |
|
show_label=False, |
|
scale=4, |
|
container=False |
|
) |
|
send_button = gr.Button("📤", scale=1, variant="primary") |
|
|
|
with gr.Row(): |
|
clear_button = gr.Button("🗑️ Limpiar", scale=1, size="sm") |
|
|
|
with gr.Column(scale=3): |
|
with gr.Row(): |
|
max_tokens = gr.Slider(100, MAX_MAX_NEW_TOKENS, DEFAULT_MAX_NEW_TOKENS, label="Tokens", container=False) |
|
temperature = gr.Slider(0.1, 2.0, 0.7, label="Temp", container=False) |
|
|
|
|
|
|
|
pais_dropdown.change( |
|
fn=actualizar_personajes, |
|
inputs=[pais_dropdown], |
|
outputs=[galeria_personajes, status_msg] |
|
) |
|
|
|
|
|
demo.load( |
|
fn=actualizar_personajes, |
|
inputs=[pais_dropdown], |
|
outputs=[galeria_personajes, status_msg] |
|
) |
|
|
|
|
|
galeria_personajes.select( |
|
fn=crear_prompt_desde_personaje, |
|
outputs=[input_box] |
|
) |
|
|
|
|
|
input_box.submit( |
|
fn=chat_function, |
|
inputs=[input_box, chatbot, max_tokens, temperature], |
|
outputs=[chatbot, input_box] |
|
) |
|
|
|
send_button.click( |
|
fn=chat_function, |
|
inputs=[input_box, chatbot, max_tokens, temperature], |
|
outputs=[chatbot, input_box] |
|
) |
|
|
|
clear_button.click( |
|
fn=lambda: ([], ""), |
|
outputs=[chatbot, input_box], |
|
queue=False |
|
) |
|
|
|
|
|
if __name__ == "__main__": |
|
if model_loaded: |
|
print("Lanzando aplicación...") |
|
demo.launch(share=False, show_error=True) |
|
else: |
|
print("Error: No se pudo cargar el modelo. Revisa la configuración.") |