De Google Maps a Juegos de Mundo Abierto: Aplicaciones del Algoritmo Theta IA

De Google Maps a Juegos de Mundo Abierto: Aplicaciones del Algoritmo Theta IA

Spread the love
Listen to this article

El documento subido trata sobre Theta*, un algoritmo de planificación de trayectorias para encontrar caminos más cortos y realistas en entornos continuos. Aquí te explico los conceptos clave y cómo funciona este algoritmo: link a ThetaIa.pdf (f3nixtech.com)

Conceptos Clave

  1. Planificación de Trayectorias:
  • Es un problema central en la inteligencia artificial para videojuegos y robótica.
  • Se divide en dos partes: discretización (convertir un entorno continuo en un gráfico) y búsqueda (propagar información a lo largo del gráfico para encontrar una ruta).
  1. Métodos de Discretización:
  • Se utilizan varias técnicas como cuadrículas regulares, gráficos de visibilidad, mapas de navegación, etc.
  • A* es el método de búsqueda más utilizado debido a su simplicidad y garantías de optimalidad.
  1. Problema con A*:
  • Encuentra rutas más largas y menos realistas ya que las restringe a los bordes del gráfico.
  • Requiere técnicas de post-procesamiento para suavizar las rutas, lo que puede ser ineficiente.

Algoritmo Theta*

Theta* es una variante del algoritmo A* que permite caminos en “cualquier ángulo” sin restringirse a los bordes del gráfico. A continuación se detalla su funcionamiento:

  1. Actualización de Vértices:
  • A diferencia de A, Theta permite que el padre de un vértice sea cualquier otro vértice con línea de visión, no solo los vecinos visibles.
  • Esto se realiza mediante dos caminos:
    • Camino 1: Similar a A*, considera el camino desde el vértice inicial hasta un vértice (s) y luego hasta un vecino visible (s’) en línea recta.
    • Camino 2: Considera el camino desde el vértice inicial hasta el padre de (s) y luego hasta (s’) en línea recta si (s’) tiene línea de visión con el padre de (s).
  1. Ventajas de Theta*:
  • Encuentra rutas más cortas y realistas que A* sin necesidad de post-procesamiento.
  • Utiliza solo unas pocas líneas más de código que A* y tiene un tiempo de ejecución similar.
  • Las rutas encontradas son casi idénticas a las rutas más cortas en el entorno continuo.
  1. Chequeos de Línea de Visión:
  • Los chequeos de línea de visión se pueden realizar de manera eficiente utilizando solo operaciones enteras, similar al trazado de líneas en gráficos por computadora.

Ejemplo Práctico

En el documento se muestra un ejemplo donde Theta* encuentra una ruta significativamente más corta y realista en comparación con A* en un mapa de un videojuego (Baldur’s Gate).

Análisis y Conclusiones

  • Theta* no siempre garantiza encontrar la ruta más corta, pero generalmente encuentra rutas muy cercanas a la óptima.
  • En promedio, las rutas de Theta* son mucho más cortas que las de A* y solo son un poco más lentas en términos de tiempo de ejecución.
  • Theta* es especialmente útil en cuadrículas grandes y mapas de videojuegos, donde las heurísticas optimizadas para A* no existen.

Si necesitas ayuda adicional para entender algún aspecto específico o cómo implementar este algoritmo, házmelo saber y te ayudaré a profundizar en los detalles.

¡Claro! Aquí tienes ejemplos de implementación de Theta* en C# y Python, junto con explicaciones detalladas de lo que hace cada parte del código.

Ejemplo en C

using System;
using System.Collections.Generic;

public class Node
{
    public int X { get; set; }
    public int Y { get; set; }
    public double G { get; set; }
    public double H { get; set; }
    public double F { get { return G + H; } }
    public Node Parent { get; set; }

    public Node(int x, int y)
    {
        X = x;
        Y = y;
        G = 0;
        H = 0;
        Parent = null;
    }
}

public class ThetaStar
{
    private int[,] grid;
    private int rows, cols;
    private List<Node> openList;
    private HashSet<Node> closedList;

    public ThetaStar(int[,] grid)
    {
        this.grid = grid;
        rows = grid.GetLength(0);
        cols = grid.GetLength(1);
        openList = new List<Node>();
        closedList = new HashSet<Node>();
    }

    private double Heuristic(Node a, Node b)
    {
        return Math.Sqrt(Math.Pow(a.X - b.X, 2) + Math.Pow(a.Y - b.Y, 2));
    }

    private bool LineOfSight(Node a, Node b)
    {
        int x0 = a.X, y0 = a.Y;
        int x1 = b.X, y1 = b.Y;
        int dx = Math.Abs(x1 - x0);
        int dy = Math.Abs(y1 - y0);
        int sx = x0 < x1 ? 1 : -1;
        int sy = y0 < y1 ? 1 : -1;
        int err = dx - dy;

        while (true)
        {
            if (grid[x0, y0] == 1) return false;
            if (x0 == x1 && y0 == y1) return true;
            int e2 = 2 * err;
            if (e2 > -dy)
            {
                err -= dy;
                x0 += sx;
            }
            if (e2 < dx)
            {
                err += dx;
                y0 += sy;
            }
        }
    }

    public List<Node> FindPath(Node start, Node goal)
    {
        start.G = 0;
        start.H = Heuristic(start, goal);
        openList.Add(start);

        while (openList.Count > 0)
        {
            openList.Sort((nodeA, nodeB) => nodeA.F.CompareTo(nodeB.F));
            Node current = openList[0];

            if (current.X == goal.X && current.Y == goal.Y)
            {
                List<Node> path = new List<Node>();
                while (current != null)
                {
                    path.Add(current);
                    current = current.Parent;
                }
                path.Reverse();
                return path;
            }

            openList.Remove(current);
            closedList.Add(current);

            for (int dx = -1; dx <= 1; dx++)
            {
                for (int dy = -1; dy <= 1; dy++)
                {
                    if (dx == 0 && dy == 0) continue;
                    int newX = current.X + dx;
                    int newY = current.Y + dy;

                    if (newX < 0 || newY < 0 || newX >= rows || newY >= cols || grid[newX, newY] == 1)
                        continue;

                    Node neighbor = new Node(newX, newY);

                    if (closedList.Contains(neighbor))
                        continue;

                    double tentativeG = current.G + Heuristic(current, neighbor);

                    if (!openList.Contains(neighbor))
                    {
                        neighbor.Parent = current;
                        neighbor.G = tentativeG;
                        neighbor.H = Heuristic(neighbor, goal);
                        openList.Add(neighbor);
                    }
                    else if (tentativeG < neighbor.G)
                    {
                        neighbor.Parent = current;
                        neighbor.G = tentativeG;
                    }

                    if (current.Parent != null && LineOfSight(current.Parent, neighbor))
                    {
                        if (current.Parent.G + Heuristic(current.Parent, neighbor) < neighbor.G)
                        {
                            neighbor.Parent = current.Parent;
                            neighbor.G = current.Parent.G + Heuristic(current.Parent, neighbor);
                        }
                    }
                }
            }
        }
        return null;
    }
}

public class Program
{
    public static void Main(string[] args)
    {
        int[,] grid = new int[5, 5]
        {
            { 0, 0, 0, 0, 0 },
            { 0, 1, 1, 1, 0 },
            { 0, 1, 0, 1, 0 },
            { 0, 1, 0, 1, 0 },
            { 0, 0, 0, 0, 0 }
        };

        Node start = new Node(0, 0);
        Node goal = new Node(4, 4);

        ThetaStar thetaStar = new ThetaStar(grid);
        List<Node> path = thetaStar.FindPath(start, goal);

        if (path != null)
        {
            foreach (Node node in path)
            {
                Console.WriteLine($"({node.X}, {node.Y})");
            }
        }
        else
        {
            Console.WriteLine("No path found");
        }
    }
}

Explicación del Código en C

  1. Clase Node:
  • Representa un nodo en la cuadrícula.
  • Contiene las coordenadas (X, Y), el costo G desde el inicio, el costo H heurístico hasta el objetivo, y el padre de cada nodo para reconstruir el camino.
  1. Clase ThetaStar:
  • Implementa el algoritmo Theta*.
  • Métodos:
    • Heuristic: Calcula la distancia euclidiana entre dos nodos.
    • LineOfSight: Verifica si hay una línea de visión directa entre dos nodos usando el algoritmo de trazado de líneas de Bresenham.
    • FindPath: Encuentra el camino desde el nodo inicial hasta el nodo objetivo.
  1. Función Principal (Main):
  • Define una cuadrícula de ejemplo.
  • Inicializa los nodos de inicio y objetivo.
  • Llama al método FindPath y muestra el camino encontrado.

Ejemplo en Python

import math
import heapq

class Node:
    def __init__(self, x, y):
        self.x = x
        self.y = y
        self.g = 0
        self.h = 0
        self.parent = None

    @property
    def f(self):
        return self.g + self.h

    def __lt__(self, other):
        return self.f < other.f

def heuristic(a, b):
    return math.sqrt((a.x - b.x) ** 2 + (a.y - b.y) ** 2)

def line_of_sight(grid, a, b):
    x0, y0 = a.x, a.y
    x1, y1 = b.x, b.y
    dx, dy = abs(x1 - x0), abs(y1 - y0)
    sx, sy = (1 if x0 < x1 else -1), (1 if y0 < y1 else -1)
    err = dx - dy

    while True:
        if grid[x0][y0] == 1:
            return False
        if x0 == x1 and y0 == y1:
            return True
        e2 = 2 * err
        if e2 > -dy:
            err -= dy
            x0 += sx
        if e2 < dx:
            err += dx
            y0 += sy

def theta_star(grid, start, goal):
    open_list = []
    closed_list = set()
    heapq.heappush(open_list, (start.f, start))

    while open_list:
        current = heapq.heappop(open_list)[1]
        if current.x == goal.x and current.y == goal.y:
            path = []
            while current:
                path.append((current.x, current.y))
                current = current.parent
            return path[::-1]

        closed_list.add((current.x, current.y))

        for dx in range(-1, 2):
            for dy in range(-1, 2):
                if dx == 0 and dy == 0:
                    continue
                new_x, new_y = current.x + dx, current.y + dy

                if (new_x < 0 or new_y < 0 or
                    new_x >= len(grid) or new_y >= len(grid[0]) or
                    grid[new_x][new_y] == 1):
                    continue

                neighbor = Node(new_x, new_y)
                if (neighbor.x, neighbor.y) in closed_list:
                    continue

                tentative_g = current.g + heuristic(current, neighbor)

                if (neighbor.x, neighbor.y) not in closed_list:
                    neighbor.parent = current
                    neighbor.g = tentative_g
                    neighbor.h = heuristic(neighbor, goal)
                    heapq.heappush(open_list, (neighbor.f, neighbor))
                else:
                    existing_node = next(n for _, n in open_list if n.x == neighbor.x and n.y == neighbor.y)
                    if tentative_g < existing_node.g:
                        existing_node.parent = current
                        existing_node.g = tentative_g

                if current.parent and line_of_sight

(grid, current.parent, neighbor):
                    if current.parent.g + heuristic(current.parent, neighbor) < neighbor.g:
                        neighbor.parent = current.parent
                        neighbor.g = current.parent.g + heuristic(current.parent, neighbor)

    return None

# Ejemplo de uso
grid = [
    [0, 0, 0, 0, 0],
    [0, 1, 1, 1, 0],
    [0, 1, 0, 1, 0],
    [0, 1, 0, 1, 0],
    [0, 0, 0, 0, 0]
]

start = Node(0, 0)
goal = Node(4, 4)

path = theta_star(grid, start, goal)
if path:
    for p in path:
        print(p)
else:
    print("No se encontró un camino")

Explicación del Código en Python

  1. Clase Node:
  • Representa un nodo en la cuadrícula.
  • Contiene las coordenadas (x, y), el costo g desde el inicio, el costo h heurístico hasta el objetivo, y el padre de cada nodo para reconstruir el camino.
  1. Funciones:
  • heuristic: Calcula la distancia euclidiana entre dos nodos.
  • line_of_sight: Verifica si hay una línea de visión directa entre dos nodos usando el algoritmo de trazado de líneas de Bresenham.
  • theta_star: Implementa el algoritmo Theta* para encontrar el camino desde el nodo inicial hasta el nodo objetivo.
  1. Ejemplo de Uso:
  • Define una cuadrícula de ejemplo.
  • Inicializa los nodos de inicio y objetivo.
  • Llama a la función theta_star y muestra el camino encontrado.

Estos ejemplos en C# y Python implementan el algoritmo Theta* para la planificación de trayectorias en una cuadrícula, permitiendo caminos más cortos y realistas al permitir movimientos en cualquier ángulo, no solo a lo largo de los bordes de la cuadrícula.

El algoritmo Theta* y los códigos proporcionados pueden ser utilizados en una variedad de aplicaciones y juegos donde la planificación de trayectorias y la navegación son cruciales. Aquí tienes cinco ejemplos:

1. Juegos de Estrategia en Tiempo Real (RTS)

Uso:

  • En juegos de estrategia en tiempo real, como StarCraft o Age of Empires, los personajes y unidades deben moverse eficientemente por el mapa.

Funcionalidad:

  • El algoritmo Theta* se usaría para calcular las trayectorias más cortas y realistas para las unidades, evitando obstáculos y encontrando caminos suaves en tiempo real.
  • Mejoraría la capacidad de las unidades para moverse de manera natural y eficiente, evitando rutas artificialmente largas.

2. Juegos de Aventura y Mundo Abierto

Uso:

  • En juegos de aventura y mundo abierto como The Legend of Zelda: Breath of the Wild o Grand Theft Auto, los personajes deben navegar por entornos complejos y variados.

Funcionalidad:

  • Theta* puede proporcionar caminos que eviten obstáculos de manera realista, permitiendo movimientos más naturales.
  • Asegura que los personajes puedan moverse a través de terrenos irregulares y encontrar rutas óptimas sin restricciones a cuadrículas estrictas.

3. Robots Autónomos en Simulaciones

Uso:

  • En simuladores de robots, como ROS (Robot Operating System) o plataformas de investigación en robótica, se necesita que los robots naveguen eficientemente en entornos simulados.

Funcionalidad:

  • Theta* permitiría a los robots planificar rutas suaves y cortas, mejorando la eficiencia energética y la velocidad de navegación.
  • Ayudaría a los robots a evitar obstáculos y navegar en entornos dinámicos.

4. Aplicaciones de Navegación y Mapas

Uso:

  • En aplicaciones de navegación, como Google Maps o Waze, para planificar rutas peatonales o ciclísticas en áreas urbanas.

Funcionalidad:

  • Theta* podría usarse para encontrar caminos más cortos y realistas que eviten obstáculos urbanos como edificios y calles congestionadas.
  • Mejoraría la precisión de las rutas ofrecidas, haciendo que las sugerencias sean más eficientes y útiles para los usuarios.

5. Juegos de Simulación y Construcción

Uso:

  • En juegos de simulación y construcción, como SimCity o Cities: Skylines, donde los personajes deben moverse por una ciudad construida por el jugador.

Funcionalidad:

  • Theta* permitiría una navegación más fluida y realista para los personajes dentro de la ciudad, mejorando la experiencia del usuario.
  • Aseguraría que los personajes encuentren rutas óptimas y eviten caminos innecesariamente largos debido a la disposición de los edificios y estructuras.

Cómo Funcionaría en Cada Caso

  1. Integración:
  • Se integraría el algoritmo Theta* en el motor del juego o aplicación para manejar la planificación de rutas.
  • Se definirían las cuadrículas y los nodos en función del mapa o entorno del juego.
  1. Inicialización:
  • En el inicio de la tarea de navegación, se inicializarían los nodos de inicio y objetivo según la posición actual y el destino deseado.
  1. Cálculo de la Ruta:
  • Theta* calcularía la ruta más corta y suave, considerando los obstáculos y posibles caminos directos entre nodos.
  • Se ejecutarían chequeos de línea de visión para permitir movimientos en ángulos directos, no solo a lo largo de las aristas de la cuadrícula.
  1. Movimiento:
  • La entidad (unidad, personaje, robot) seguiría la ruta calculada, moviéndose de nodo en nodo de manera eficiente.
  • Se actualizarían las posiciones en tiempo real para reflejar cambios en el entorno o nuevas órdenes de movimiento.

Conclusión

El algoritmo Theta* es altamente beneficioso en aplicaciones y juegos donde la navegación precisa y eficiente es crucial. Mejora significativamente la calidad de los movimientos, ofreciendo rutas más cortas y naturales en comparación con algoritmos tradicionales como A*. Esto se traduce en una mejor experiencia de usuario y mayor eficiencia en diversas aplicaciones.

Sí, se podría hacer un programa de navegación similar a Google Maps utilizando la lógica de Theta* para encontrar rutas eficientes. A continuación, se describe cómo se podría implementar este tipo de programa, destacando los pasos clave y cómo funcionaría cada parte del sistema.

Componentes Clave del Sistema

  1. Base de Datos de Mapas:
  • Una base de datos que almacene información detallada sobre las calles, intersecciones, obstáculos, y puntos de interés.
  • Esta base de datos podría incluir información de OpenStreetMap o una base de datos similar.
  1. Algoritmo de Planificación de Rutas:
  • Implementación del algoritmo Theta* para calcular rutas óptimas.
  • Adaptación del algoritmo para manejar datos en un formato adecuado para mapas urbanos (como calles y carreteras).
  1. Interfaz de Usuario:
  • Una interfaz gráfica donde los usuarios puedan ingresar su ubicación de inicio y destino.
  • Visualización del mapa y la ruta calculada.
  1. Servidores y APIs:
  • Servidores para manejar las solicitudes de los usuarios y ejecutar los cálculos de rutas.
  • APIs para facilitar la comunicación entre la aplicación de usuario y el servidor.

Pasos para la Implementación

1. Adquirir y Preparar los Datos del Mapa

  • Obtener Datos:
  • Utilizar datos de OpenStreetMap (OSM) que contienen información sobre carreteras, edificios, y otros elementos geográficos.
  • Procesar los Datos:
  • Convertir los datos de OSM en una estructura de datos adecuada para la planificación de rutas (por ejemplo, una cuadrícula o un gráfico de nodos y aristas).

2. Implementar el Algoritmo Theta*

  • Adaptar Theta* para Datos de Carreteras:
  • Modificar el algoritmo para manejar carreteras en lugar de cuadrículas simples.
  • Asegurar que el algoritmo considere restricciones como direcciones de tráfico y tipos de carreteras.

3. Desarrollar la Interfaz de Usuario

  • Crear la UI:
  • Utilizar herramientas como HTML, CSS, y JavaScript (para aplicaciones web) o tecnologías móviles (como React Native) para construir la interfaz.
  • Integrar un mapa interactivo utilizando bibliotecas como Leaflet o Google Maps API para visualizar los datos.
  • Ingreso de Ubicaciones:
  • Permitir a los usuarios ingresar ubicaciones de inicio y destino.
  • Usar servicios de geocodificación para convertir direcciones en coordenadas geográficas.

4. Servidor y APIs

  • Desarrollar el Servidor:
  • Utilizar un framework como Flask (Python) o Express (Node.js) para construir el servidor que manejará las solicitudes de rutas.
  • Endpoints de la API:
  • Crear endpoints para recibir solicitudes de rutas, procesarlas y devolver los resultados.
  • Ejemplo de un endpoint: /api/get_route?start=lat1,lon1&end=lat2,lon2

5. Planificación de Rutas en el Servidor

  • Calcular la Ruta:
  • Al recibir una solicitud de ruta, el servidor utilizará el algoritmo Theta* para calcular la ruta óptima entre los puntos de inicio y destino.
  • Enviar la Ruta al Cliente:
  • El servidor devuelve la ruta calculada (como una serie de coordenadas) a la aplicación del cliente.

Ejemplo Simplificado de Código

Servidor (Python con Flask)

from flask import Flask, request, jsonify
import math
import heapq

app = Flask(__name__)

class Node:
    def __init__(self, x, y):
        self.x = x
        self.y = y
        self.g = 0
        self.h = 0
        self.parent = None

    @property
    def f(self):
        return self.g + self.h

    def __lt__(self, other):
        return self.f < other.f

def heuristic(a, b):
    return math.sqrt((a.x - b.x) ** 2 + (a.y - b.y) ** 2)

def line_of_sight(grid, a, b):
    # Implementar el chequeo de línea de visión similar al ejemplo anterior
    pass

def theta_star(grid, start, goal):
    open_list = []
    closed_list = set()
    heapq.heappush(open_list, (start.f, start))

    while open_list:
        current = heapq.heappop(open_list)[1]
        if current.x == goal.x and current.y == goal.y:
            path = []
            while current:
                path.append((current.x, current.y))
                current = current.parent
            return path[::-1]

        closed_list.add((current.x, current.y))

        for dx in range(-1, 2):
            for dy in range(-1, 2):
                if dx == 0 and dy == 0:
                    continue
                new_x, new_y = current.x + dx, current.y + dy

                if (new_x < 0 or new_y < 0 or
                    new_x >= len(grid) or new_y >= len(grid[0]) or
                    grid[new_x][new_y] == 1):
                    continue

                neighbor = Node(new_x, new_y)
                if (neighbor.x, neighbor.y) in closed_list:
                    continue

                tentative_g = current.g + heuristic(current, neighbor)

                if (neighbor.x, neighbor.y) not in closed_list:
                    neighbor.parent = current
                    neighbor.g = tentative_g
                    neighbor.h = heuristic(neighbor, goal)
                    heapq.heappush(open_list, (neighbor.f, neighbor))
                else:
                    existing_node = next(n for _, n in open_list if n.x == neighbor.x and n.y == neighbor.y)
                    if tentative_g < existing_node.g:
                        existing_node.parent = current
                        existing_node.g = tentative_g

                if current.parent and line_of_sight(grid, current.parent, neighbor):
                    if current.parent.g + heuristic(current.parent, neighbor) < neighbor.g:
                        neighbor.parent = current.parent
                        neighbor.g = current.parent.g + heuristic(current.parent, neighbor)

    return None

@app.route('/api/get_route', methods=['GET'])
def get_route():
    start_coords = request.args.get('start').split(',')
    end_coords = request.args.get('end').split(',')
    start = Node(int(start_coords[0]), int(start_coords[1]))
    goal = Node(int(end_coords[0]), int(end_coords[1]))

    # Ejemplo de una cuadrícula de 5x5 (se debe reemplazar con los datos reales del mapa)
    grid = [
        [0, 0, 0, 0, 0],
        [0, 1, 1, 1, 0],
        [0, 1, 0, 1, 0],
        [0, 1, 0, 1, 0],
        [0, 0, 0, 0, 0]
    ]

    path = theta_star(grid, start, goal)
    if path:
        return jsonify({'path': path})
    else:
        return jsonify({'error': 'No path found'}), 404

if __name__ == '__main__':
    app.run(debug=True)

Cliente (HTML y JavaScript)

<!DOCTYPE html>
<html>
<head>
    <title>Ruta con Theta*</title>
    <script src="https://unpkg.com/leaflet/dist/leaflet.js"></script>
    <link rel="stylesheet" href="https://unpkg.com/leaflet/dist/leaflet.css"/>
    <style>
        #map {
            height: 600px;
            width: 100%;
        }
    </style>
</head>
<body>
    <h1>Ruta con Theta*</h1>
    <div id="map"></div>

    <script>
        var map = L.map('map').setView([51.505, -0.09], 13);

        L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
            maxZoom: 19
        }).addTo(map);

        function getRoute() {
            fetch('/api/get_route?start=0,0&end=4,4')
                .then(response => response.json())
                .then(data => {
                    if (data.path) {
                        var latlngs = data.path.map(p => [p[0], p[1]]);
                        var polyline = L.polyline(latlngs, {color: 'red'}).addTo(map);
                        map.fitBounds(polyline.getBounds());
                    } else {
                        alert('No se encontró un camino');
                    }
                });
        }

        getRoute();
    </script>
</body>
</html>

Explicación de Funcionamiento

  1. Servidor y API:
  • El servidor en Flask maneja solicitudes GET en el endpoint /api/get_route.
  • Cuando se solicita una ruta, el servidor usa el algoritmo Theta* para calcular la ruta entre el punto de inicio y el destino.
  • Devuelve la ruta calculada como una lista de coordenadas.
  1. Interfaz de Usuario:
  • La interfaz web utiliza Leaflet para mostrar un mapa interactivo.
  • Cuando se carga la página, se llama a la función getRoute que solicita la ruta al servidor.
  • La ruta calculada se dibuja en el mapa utilizando una polilínea.

Beneficios y Consideraciones

  • Precisión:
  • El uso de Theta* proporciona rutas más

0 0 votos
Article Rating
Suscribir
Notificar de
guest
0 Comments
Más antiguo
La mas nueva Más votado
Comentarios en línea
Ver todos los comentarios