Construyendo una API GraphQL con Django

    Introducci贸n

    Las API web son los motores que impulsan la mayor铆a de nuestras aplicaciones en la actualidad. Durante muchos a帽os, REST ha sido la arquitectura dominante para las API, pero en este art铆culo exploraremos GraphQL.

    Con las API REST, generalmente crea URL para cada objeto de datos que es accesible. Digamos que estamos construyendo una API REST para pel铆culas: tendremos URL para las pel铆culas en s铆, actores, premios, directores, productores … 隆ya se est谩 volviendo dif铆cil de manejar! Esto podr铆a significar muchas solicitudes de un lote de datos relacionados. Imagina que eres el usuario de un tel茅fono m贸vil de baja potencia con una conexi贸n a Internet lenta, esta situaci贸n no es ideal.

    GraphQL no es una arquitectura API como REST, es un lenguaje que nos permite compartir datos relacionados de una manera mucho m谩s f谩cil. Lo usaremos para dise帽ar una API para pel铆culas. Luego, veremos c贸mo la biblioteca Graphene nos permite construir API en Python al hacer una API de pel铆cula con Django.

    Que es GraphQL

    Originalmente creado por Facebook pero ahora desarrollado bajo GraphQL Foundation, GraphQL es un lenguaje de consulta y tiempo de ejecuci贸n de servidor que nos permite recuperar y manipular datos.

    Aprovechamos el sistema fuertemente tipado de GraphQL para definir los datos que queremos que est茅n disponibles para la API. Luego, creamos un esquema para la API: el conjunto de consultas permitidas para recuperar y modificar datos.

    Dise帽ar un esquema de pel铆cula

    Creando Nuestros Tipos

    Los tipos describen el tipo de datos que est谩n disponibles en la API. Ya existen tipos primitivos proporcionados que podemos usar, pero tambi茅n podemos definir nuestros propios tipos personalizados.

    Considere los siguientes tipos de actores y pel铆culas:

    type Actor {
      id: ID!
      name: String!
    }
    
    type Movie {
      id: ID!
      title: String!
      actors: [Actor]
      year: Int!
    }
    

    los ID type nos dice que el campo es el identificador 煤nico para ese tipo de datos. Si el ID no es una cadena, el tipo necesita una forma de serializarse en una cadena para que funcione.

    Nota: El signo de exclamaci贸n significa que el campo es obligatorio.

    Tambi茅n notar铆as que en Movie usamos ambos tipos primitivos como String y Int as铆 como nuestra costumbre Actor tipo.

    Si queremos que un campo contenga la lista del tipo, lo encerramos entre corchetes – [Actor].

    Crear consultas

    Una consulta especifica qu茅 datos se pueden recuperar y qu茅 se requiere para acceder a ellos:

    type Query {
      actor(id: ID!): Actor
      movie(id: ID!): Movie
      actors: [Actor]
      movies: [Movie]
    }
    

    Esta Query tipo nos permite obtener el Actor y Movie datos proporcionando su IDs, o podemos obtener una lista de ellos sin filtrar.

    Creando mutaciones

    Una mutaci贸n describe qu茅 operaciones se pueden realizar para cambiar los datos en el servidor.

    Las mutaciones se basan en dos cosas:

    • Entradas – tipos especiales que solo se utilizan como argumentos en una mutaci贸n cuando queremos pasar un objeto completo en lugar de campos individuales.
    • Cargas 煤tiles – tipos regulares, pero por convenci贸n los usamos como salidas para una mutaci贸n para que podamos extenderlos f谩cilmente a medida que evoluciona la API.

    Lo primero que hacemos es crear los tipos de entrada:

    input ActorInput {
      id: ID
      name: String!
    }
    
    input MovieInput {
      id: ID
      title: String
      actors: [ActorInput]
      year: Int
    }
    

    Y luego creamos los tipos de carga 煤til:

    type ActorPayload {
      ok: Boolean
      actor: Actor
    }
    
    type MoviePayload {
      ok: Boolean
      movie: Movie
    }
    

    Toma nota del ok campo, es com煤n que los tipos de carga 煤til incluyan metadatos como un estado o un campo de error.

    los Mutation type lo re煤ne todo:

    type Mutation {
      createActor(input: ActorInput) : ActorPayload
      createMovie(input: MovieInput) : MoviePayload
      updateActor(id: ID!, input: ActorInput) : ActorPayload
      updateMovie(id: ID!, input: MovieInput) : MoviePayload
    }
    

    los createActor el mutador necesita un ActorInput objeto, que requiere el nombre del actor.

    los updateActor mutador requiere el ID del actor que se actualiza, as铆 como la informaci贸n actualizada.

    Lo mismo ocurre con los mutantes de la pel铆cula.

    Nota: Mientras que la ActorPayload y MoviePayload no son necesarios para una mutaci贸n exitosa, es una buena pr谩ctica que las API proporcionen comentarios cuando procesan una acci贸n.

    Definiendo el esquema

    Finalmente, asignamos las consultas y mutaciones que hemos creado al esquema:

    schema {
      query: Query
      mutation: Mutation
    }
    

    Usando la biblioteca de grafeno

    GraphQL es independiente de la plataforma, uno puede crear un servidor GraphQL con una variedad de lenguajes de programaci贸n (Java, PHP, Go), frameworks (Node.js, Symfony, Rails) o plataformas como Apollo.

    Con Grafeno, no tenemos que usar la sintaxis de GraphQL para crear un esquema, 隆solo usamos Python! Esta biblioteca de c贸digo abierto tambi茅n se ha integrado con Django para que podamos crear esquemas haciendo referencia a los modelos de nuestra aplicaci贸n.

    Configuraci贸n de la aplicaci贸n

    Ambientes virtuales

    Se considera la mejor pr谩ctica para crear entornos virtuales para proyectos de Django. Desde Python 3.6, el venv Se ha incluido un m贸dulo para crear y gestionar entornos virtuales.

    Usando la terminal, ingrese a su espacio de trabajo y cree la siguiente carpeta:

    $ mkdir django_graphql_movies
    $ cd django_graphql_movies/
    

    Ahora crea el entorno virtual:

    $ python3 -m venv env
    

    Deber铆as ver un nuevo env carpeta en su directorio. Necesitamos activar nuestro entorno virtual, para que cuando instalemos paquetes de Python solo est茅n disponibles para este proyecto y no para todo el sistema:

    $ . env/bin/activate
    

    Nota: Para salir del entorno virtual y utilizar su shell habitual, escriba deactivate. Debe hacer esto al final del tutorial.

    Instalaci贸n y configuraci贸n de Django y Graphene

    Mientras estamos en nuestro entorno virtual, usamos pip para instalar Django y la biblioteca Graphene:

    $ pip install Django
    $ pip install graphene_django
    

    Luego creamos nuestro proyecto Django:

    $ django-admin.py startproject django_graphql_movies .
    

    Un proyecto de Django puede constar de muchas aplicaciones. Las aplicaciones son componentes reutilizables dentro de un proyecto, y es una buena pr谩ctica crear nuestro proyecto con ellas. Creemos una aplicaci贸n para nuestras pel铆culas:

    $ cd django_graphql_movies/
    $ django-admin.py startapp movies
    

    Antes de trabajar en nuestra aplicaci贸n o ejecutarla, sincronizaremos nuestras bases de datos:

    # First return to the project's directory
    $ cd ..
    # And then run the migrate command
    $ python manage.py migrate
    

    Creando un modelo

    Los modelos de Django describen el dise帽o de la base de datos de nuestro proyecto. Cada modelo es una clase de Python que generalmente se asigna a una tabla de base de datos. Las propiedades de la clase se asignan a las columnas de la base de datos.

    Escriba el siguiente c贸digo para django_graphql_movies/movies/models.py:

    from django.db import models
    
    class Actor(models.Model):
        name = models.CharField(max_length=100)
    
        def __str__(self):
            return self.name
    
        class Meta:
            ordering = ('name',)
    
    class Movie(models.Model):
        title = models.CharField(max_length=100)
        actors = models.ManyToManyField(Actor)
        year = models.IntegerField()
    
        def __str__(self):
            return self.title
    
        class Meta:
            ordering = ('title',)
    

    Al igual que con el esquema GraphQL, el Actor modelo tiene un nombre mientras que el Movie modelo tiene un t铆tulo, una relaci贸n de muchos a muchos con los actores y un a帽o. Django genera autom谩ticamente los ID.

    Ahora podemos registrar nuestra aplicaci贸n de pel铆culas dentro del proyecto. Ir al django_graphql_movies/settings.py y cambia el INSTALLED_APPS a lo siguiente:

    INSTALLED_APPS = [
        'django.contrib.admin',
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.messages',
        'django.contrib.staticfiles',
        'django_graphql_movies.movies',
    ]
    

    Aseg煤rese de migrar su base de datos para mantenerla sincronizada con nuestros cambios de c贸digo:

    $ python manage.py makemigrations
    $ python manage.py migrate
    

    Cargando datos de prueba

    Despu茅s de que construyamos nuestra API, querremos poder realizar consultas para probar si funciona. Carguemos algunos datos en nuestra base de datos, guarde el siguiente JSON como movies.json en el directorio ra铆z de su proyecto:

    [
      {
        "model": "movies.actor",
        "pk": 1,
        "fields": {
          "name": "Michael B. Jordan"
        }
      },
      {
        "model": "movies.actor",
        "pk": 2,
        "fields": {
          "name": "Sylvester Stallone"
        }
      },
      {
        "model": "movies.movie",
        "pk": 1,
        "fields": {
          "title": "Creed",
          "actors": [1, 2],
          "year": "2015"
        }
      }
    ]
    

    Y ejecute el siguiente comando para cargar los datos de prueba:

    $ python manage.py loaddata movies.json
    

    Deber铆a ver la siguiente salida en la terminal:

    Installed 3 object(s) from 1 fixture(s)
    

    Creando nuestro esquema con grafeno

    Realizaci贸n de consultas

    En nuestra carpeta de aplicaciones de pel铆culas, cree una nueva schema.py file y definamos nuestros tipos GraphQL:

    import graphene
    from graphene_django.types import DjangoObjectType, ObjectType
    from django_graphql_movies.movies.models import Actor, Movie
    
    # Create a GraphQL type for the actor model
    class ActorType(DjangoObjectType):
        class Meta:
            model = Actor
    
    # Create a GraphQL type for the movie model
    class MovieType(DjangoObjectType):
        class Meta:
            model = Movie
    

    Con la ayuda de Graphene, para crear un tipo GraphQL simplemente especificamos qu茅 modelo de Django tiene las propiedades que queremos en la API.

    En el mismo archivo, agregue el siguiente c贸digo para crear el Query tipo:

    # Create a Query type
    class Query(ObjectType):
        actor = graphene.Field(ActorType, id=graphene.Int())
        movie = graphene.Field(MovieType, id=graphene.Int())
        actors = graphene.List(ActorType)
        movies= graphene.List(MovieType)
    
        def resolve_actor(self, info, **kwargs):
            id = kwargs.get('id')
    
            if id is not None:
                return Actor.objects.get(pk=id)
    
            return None
    
        def resolve_movie(self, info, **kwargs):
            id = kwargs.get('id')
    
            if id is not None:
                return Movie.objects.get(pk=id)
    
            return None
    
        def resolve_actors(self, info, **kwargs):
            return Actor.objects.all()
    
        def resolve_movies(self, info, **kwargs):
            return Movie.objects.all()
    

    Cada propiedad del Query clase corresponde a una consulta GraphQL:

    • los actor y movie propiedades devuelven un valor de ActorType y MovieType respectivamente, y ambos requieren un ID que sea un n煤mero entero.
    • los actors y movies las propiedades devuelven una lista de sus respectivos tipos.

    Los cuatro m茅todos que creamos en la clase Query se llaman resolutores. Los solucionadores conectan las consultas en el esquema con las acciones reales realizadas por la base de datos. Como es est谩ndar en Django, interactuamos con nuestra base de datos a trav茅s de modelos.

    Considera el resolve_actor funci贸n. Recuperamos el ID de los par谩metros de consulta y devolvemos el actor de nuestra base de datos con ese ID como clave principal. los resolve_actors La funci贸n simplemente obtiene todos los actores de la base de datos y los devuelve como una lista.

    Haciendo mutaciones

    Cuando dise帽amos el esquema, primero creamos tipos de entrada especiales para nuestras mutaciones. Hagamos lo mismo con el grafeno, agregue esto a schema.py:

    # Create Input Object Types
    class ActorInput(graphene.InputObjectType):
        id = graphene.ID()
        name = graphene.String()
    
    class MovieInput(graphene.InputObjectType):
        id = graphene.ID()
        title = graphene.String()
        actors = graphene.List(ActorInput)
        year = graphene.Int()
    

    Son clases simples que definen qu茅 campos se pueden usar para cambiar datos en la API.

    La creaci贸n de mutaciones requiere un poco m谩s de trabajo que la creaci贸n de consultas. Agreguemos las mutaciones para los actores:

    # Create mutations for actors
    class CreateActor(graphene.Mutation):
        class Arguments:
            input = ActorInput(required=True)
    
        ok = graphene.Boolean()
        actor = graphene.Field(ActorType)
    
        @staticmethod
        def mutate(root, info, input=None):
            ok = True
            actor_instance = Actor(name=input.name)
            actor_instance.save()
            return CreateActor(ok=ok, actor=actor_instance)
    
    class UpdateActor(graphene.Mutation):
        class Arguments:
            id = graphene.Int(required=True)
            input = ActorInput(required=True)
    
        ok = graphene.Boolean()
        actor = graphene.Field(ActorType)
    
        @staticmethod
        def mutate(root, info, id, input=None):
            ok = False
            actor_instance = Actor.objects.get(pk=id)
            if actor_instance:
                ok = True
                actor_instance.name = input.name
                actor_instance.save()
                return UpdateActor(ok=ok, actor=actor_instance)
            return UpdateActor(ok=ok, actor=None)
    

    Recuerde la firma del createActor mutaci贸n cuando dise帽amos nuestro esquema:

    createActor(input: ActorInput) : ActorPayload
    
    • El nombre de nuestra clase corresponde al nombre de la consulta de GraphQL.
    • El interior Arguments las propiedades de la clase corresponden a los argumentos de entrada para el mutador.
    • los ok y actor propiedades componen el ActorPayload.

    La clave a saber al escribir un mutation El m茅todo es que est谩 guardando los datos en el modelo Django:

    • Tomamos el nombre del objeto de entrada y creamos un nuevo Actor objeto.
    • Llamamos al save funci贸n para que nuestra base de datos se actualice y devuelva la carga 煤til al usuario.

    los UpdateActor La clase tiene una configuraci贸n similar con l贸gica adicional para recuperar el actor que se est谩 actualizando y cambiar sus propiedades antes de guardar.

    Ahora agreguemos la mutaci贸n para pel铆culas:

    # Create mutations for movies
    class CreateMovie(graphene.Mutation):
        class Arguments:
            input = MovieInput(required=True)
    
        ok = graphene.Boolean()
        movie = graphene.Field(MovieType)
    
        @staticmethod
        def mutate(root, info, input=None):
            ok = True
            actors = []
            for actor_input in input.actors:
              actor = Actor.objects.get(pk=actor_input.id)
              if actor is None:
                return CreateMovie(ok=False, movie=None)
              actors.append(actor)
            movie_instance = Movie(
              title=input.title,
              year=input.year
              )
            movie_instance.save()
            movie_instance.actors.set(actors)
            return CreateMovie(ok=ok, movie=movie_instance)
    
    
    class UpdateMovie(graphene.Mutation):
        class Arguments:
            id = graphene.Int(required=True)
            input = MovieInput(required=True)
    
        ok = graphene.Boolean()
        movie = graphene.Field(MovieType)
    
        @staticmethod
        def mutate(root, info, id, input=None):
            ok = False
            movie_instance = Movie.objects.get(pk=id)
            if movie_instance:
                ok = True
                actors = []
                for actor_input in input.actors:
                  actor = Actor.objects.get(pk=actor_input.id)
                  if actor is None:
                    return UpdateMovie(ok=False, movie=None)
                  actors.append(actor)
                movie_instance.title=input.title
                movie_instance.year=input.year
                movie_instance.save()
                movie_instance.actors.set(actors)
                return UpdateMovie(ok=ok, movie=movie_instance)
            return UpdateMovie(ok=ok, movie=None)
    

    Como las pel铆culas hacen referencia a los actores, tenemos que recuperar los datos de los actores de la base de datos antes de guardarlos. los for loop primero verifica que los actores proporcionados por el usuario est茅n efectivamente en la base de datos, si no, regresa sin guardar ning煤n dato.

    Cuando trabajamos con relaciones de muchos a muchos en Django, solo podemos guardar datos relacionados despu茅s de que se guarde nuestro objeto.

    Por eso guardamos nuestra pel铆cula con movie_instance.save() antes de poner a los actores con movie_instance.actors.set(actors).

    Para completar nuestras mutaciones, creamos el tipo de mutaci贸n:

    class Mutation(graphene.ObjectType):
        create_actor = CreateActor.Field()
        update_actor = UpdateActor.Field()
        create_movie = CreateMovie.Field()
        update_movie = UpdateMovie.Field()
    

    Haciendo el esquema

    Como antes, cuando dise帽amos nuestro esquema, asignamos las consultas y mutaciones a la API de nuestra aplicaci贸n. Agregue esto al final de schema.py:

    schema = graphene.Schema(query=Query, mutation=Mutation)
    

    Registro del esquema en el proyecto

    Para que nuestra API funcione, necesitamos hacer que un esquema est茅 disponible para todo el proyecto.

    Crear un nuevo schema.py presentar en django_graphql_movies/ y agregue lo siguiente:

    import graphene
    import django_graphql_movies.movies.schema
    
    class Query(django_graphql_movies.movies.schema.Query, graphene.ObjectType):
        # This class will inherit from multiple Queries
        # as we begin to add more apps to our project
        pass
    
    class Mutation(django_graphql_movies.movies.schema.Mutation, graphene.ObjectType):
        # This class will inherit from multiple Queries
        # as we begin to add more apps to our project
        pass
    
    schema = graphene.Schema(query=Query, mutation=Mutation)
    

    Desde aqu铆 podemos registrar el grafeno y decirle que use nuestro esquema.

    Abierto django_graphql_movies/settings.py y a帽adir 'graphene_django', como primer elemento de la INSTALLED_APPS.

    En el mismo archivo, agregue el siguiente c贸digo un par de l铆neas nuevas debajo del INSTALLED_APPS:

    GRAPHENE = {
        'SCHEMA': 'django_graphql_movies.schema.schema'
    }
    

    Las API de GraphQL se alcanzan a trav茅s de un punto final, /graphql. Necesitamos registrar esa ruta, o m谩s bien vista, en Django.

    Abierto django_graphql_movies/urls.py y cambie el contenido del archivo a:

    from django.contrib import admin
    from django.urls import path
    from graphene_django.views import GraphQLView
    from django_graphql_movies.schema import schema
    
    urlpatterns = [
        path('admin/', admin.site.urls),
        path('graphql/', GraphQLView.as_view(graphiql=True)),
    ]
    

    Probando nuestra API

    Para probar nuestra API, ejecutemos el proyecto y luego vayamos al punto final GraphQL. En el tipo de terminal:

    $ python manage.py runserver
    

    Una vez que su servidor est茅 funcionando, dir铆jase a http://127.0.0.1:8000/graphql/. Te encontrar谩s GraphiQL – 隆un IDE integrado para ejecutar sus consultas!

    Escribir consultas

    Para nuestra primera consulta, obtengamos todos los actores en nuestra base de datos. En el panel superior izquierdo, ingrese lo siguiente:

    query getActors {
      actors {
        id
        name
      }
    }
    

    Este es el formato para una consulta en GraphQL. Comenzamos con el query palabra clave, seguida de un nombre opcional para la consulta. Es una buena pr谩ctica dar un nombre a las consultas, ya que ayuda con el registro y la depuraci贸n. GraphQL nos permite especificar los campos que queremos tambi茅n – elegimos id y name.

    Aunque solo tenemos una pel铆cula en nuestros datos de prueba, probemos el movie consulta y descubre otra gran caracter铆stica de GraphQL:

    query getMovie {
      movie(id: 1) {
        id
        title
        actors {
          id
          name
        }
      }
    }
    

    los movie La consulta requiere un ID, por lo que proporcionamos uno entre par茅ntesis. Lo interesante viene con el actors campo. En nuestro modelo de Django incluimos el actors propiedad en nuestra Movie class y especific贸 una relaci贸n de varios a varios entre ellos. Esto nos permite recuperar todas las propiedades de un Actor tipo que est谩 relacionado con los datos de la pel铆cula.

    Este recorrido de datos en forma de gr谩fico es una de las principales razones por las que GraphQL se considera una tecnolog铆a poderosa y emocionante.

    Escritura de mutaciones

    Las mutaciones siguen un estilo similar a las consultas. Agreguemos un actor a nuestra base de datos:

    mutation createActor {
      createActor(input: {
        name: "Tom Hanks"
      }) {
        ok
        actor {
          id
          name
        }
      }
    }
    

    Note como el input par谩metro corresponde al input propiedades del Arguments clases que creamos anteriormente.

    Tambi茅n observe c贸mo ok y actor los valores de retorno se asignan a las propiedades de clase del CreateActor mutaci贸n.

    Ahora podemos agregar una pel铆cula en la que actu贸 Tom Hanks:

    mutation createMovie {
      createMovie(input: {
        title: "Cast Away",
        actors: [
          {
            id: 3
          }
        ]
        year: 1999
      }) {
        ok
        movie{
          id
          title
          actors {
            id
            name
          }
          year
        }
      }
    }
    

    Desafortunadamente, cometimos un error. 隆”Cast Away” sali贸 en el a帽o 2000!

    Ejecutemos una consulta de actualizaci贸n para solucionarlo:

    mutation updateMovie {
      updateMovie(id: 2, input: {
        title: "Cast Away",
        actors: [
          {
            id: 3
          }
        ]
        year: 2000
      }) {
        ok
        movie{
          id
          title
          actors {
            id
            name
          }
          year
        }
      }
    }
    

    隆Ah铆, todo arreglado!

    Comunicaci贸n v铆a POST

    GraphiQL es muy 煤til durante el desarrollo, pero es una pr谩ctica est谩ndar deshabilitar esa vista en producci贸n, ya que puede permitir que un desarrollador externo tenga demasiada informaci贸n sobre la API.

    Para deshabilitar GraphiQL, simplemente edite django_graphql_movies/urls.py tal que path('graphql/', GraphQLView.as_view(graphiql=True)), se convierte en path('graphql/', GraphQLView.as_view(graphiql=False)),.

    Una aplicaci贸n que se comunica con su API enviar铆a solicitudes POST al /graphql punto final. Antes de que podamos realizar solicitudes POST desde fuera del sitio de Django, debemos cambiar django_graphql_movies/urls.py:

    from django.contrib import admin
    from django.urls import path
    from graphene_django.views import GraphQLView
    from django_graphql_movies.schema import schema
    from django.views.decorators.csrf import csrf_exempt # New library
    
    urlpatterns = [
        path('admin/', admin.site.urls),
        path('graphql/', csrf_exempt(GraphQLView.as_view(graphiql=True))),
    ]
    

    Django viene integrado con CSRF Protecci贸n (Falsificaci贸n de solicitudes entre sitios): tiene medidas para evitar que los usuarios autenticados incorrectamente del sitio realicen acciones potencialmente maliciosas.

    Si bien esta es una protecci贸n 煤til, evitar铆a que las aplicaciones externas se comuniquen con la API. Deber铆a considerar otras formas de autenticaci贸n si pone su aplicaci贸n en producci贸n.

    En su terminal ingrese lo siguiente para obtener todos los actores:

    $ curl 
      -X POST 
      -H "Content-Type: application/json" 
      --data '{ "query": "{ actors { name } }" }' 
      http://127.0.0.1:8000/graphql/
    

    Tu deber铆as recibir:

    {"data":{"actors":[{"name":"Michael B. Jordan"},{"name":"Sylvester Stallone"},{"name":"Tom Hanks"}]}}
    

    Conclusi贸n

    GraphQL es un lenguaje de consulta fuertemente tipado que ayuda a crear API evolutivas. Dise帽amos un esquema de API para pel铆culas, creando los tipos, consultas y mutaciones necesarias para obtener y cambiar datos.

    Con Graphene podemos usar Django para crear API GraphQL. Implementamos el esquema de pel铆cula que dise帽amos anteriormente y lo probamos usando consultas GraphQL a trav茅s de GraphiQL y una solicitud POST est谩ndar.

    Si desea ver el c贸digo fuente de la aplicaci贸n completa, puede encontrarlo aqu铆.

    Etiquetas:

    Deja una respuesta

    Tu direcci贸n de correo electr贸nico no ser谩 publicada. Los campos obligatorios est谩n marcados con *