Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/lffiesco-svg/gastromovil/llms.txt

Use this file to discover all available pages before exploring further.

Every restaurant in GastroMóvil is owned by a single Usuario with rol='restaurante'. A matching Restaurante record is created automatically the moment that user is registered. From there, the owner uses a JavaScript-powered panel at /panel-restaurante/ to build their menu by creating categories, adding products to each category, and toggling product availability — all without leaving the page. Images for both the restaurant banner and individual products are stored on Cloudinary.

Data Models

Restaurante

The top-level model that represents a single food business on the platform:
class Restaurante(models.Model):
    propietario = models.ForeignKey('usuarios.Usuario', on_delete=models.CASCADE,
                                    related_name='restaurantes')
    nombre      = models.CharField(max_length=200)
    direccion   = models.CharField(max_length=200)
    telefono    = models.CharField(max_length=15)
    activo      = models.BooleanField(default=True)
    imagen      = CloudinaryField(
                    'image',
                    blank=True, null=True,
                    validators=[validar_png, validar_dimensiones_restaurante],
                    help_text='PNG de exactamente 400×250 px con fondo transparente.'
                  )

Categoria

A menu section that belongs to one restaurant. Products are always attached to a category, never directly to a restaurant:
class Categoria(models.Model):
    restaurante = models.ForeignKey(Restaurante, on_delete=models.CASCADE,
                                    related_name='categorias')
    nombre      = models.CharField(max_length=100)

Producto

An individual menu item nested inside a category:
class Producto(models.Model):
    categoria   = models.ForeignKey(Categoria, on_delete=models.CASCADE,
                                    related_name='productos')
    nombre      = models.CharField(max_length=200)
    descripcion = models.TextField(blank=True)
    precio      = models.DecimalField(max_digits=10, decimal_places=2)
    disponible  = models.BooleanField(default=True)
    imagen      = CloudinaryField('image', blank=True, null=True)
The disponible field is a simple toggle. Only products where disponible=True appear in the public restaurant page and are served to the AI chatbot’s product context.

Image Validation

Restaurant banner images are validated by two custom validators before being sent to Cloudinary:
def validar_png(imagen):
    if not imagen.name.lower().endswith('.png'):
        raise ValidationError('Solo se permiten imágenes en formato PNG.')

def validar_dimensiones_restaurante(imagen):
    img = PilImage.open(imagen)
    ancho, alto = img.size
    if ancho != 400 or alto != 250:
        raise ValidationError(
            f'La imagen debe ser exactamente 400×250 px. '
            f'La tuya es {ancho}×{alto} px.'
        )
Both validators run on the Restaurante.imagen field. The upload is rejected server-side if the file is not a PNG or if its dimensions are not exactly 400 × 250 px. Product images (Producto.imagen) have no dimension constraint.

Restaurant Panel API

The panel at /panel-restaurante/ is a single-page experience that talks to a set of session-authenticated REST API endpoints. All endpoints require the user to be logged in (IsAuthenticated) and implicitly scope results to the requesting user’s restaurant.

Restaurant Info

MethodEndpointDescription
GET/api/restaurantes/<pk>/Fetch restaurant details (id, nombre, direccion, telefono, activo)
PUT/api/restaurantes/<pk>/editar/Update restaurant info (supports partial updates)

Products

MethodEndpointDescription
GET/api/productos/List all products belonging to the owner’s restaurant, including category name, availability, and Cloudinary image URL
POST/api/productos/crear/Create a new product; accepts multipart/form-data for image upload
PUT/api/productos/<pk>/editar/Edit a product; optionally replace its image
DELETE/api/productos/<pk>/eliminar/Delete a product

Categories

MethodEndpointDescription
GET/api/categorias/List all categories belonging to the owner’s restaurant
POST/api/categorias/crear/Create a new category
PUT/api/categorias/<pk>/editar/Rename a category
DELETE/api/categorias/<pk>/eliminar/Delete a category (cascades to its products)
All panel API endpoints enforce ownership — for example, producto_eliminar_api uses get_object_or_404(Producto, pk=pk, categoria__restaurante__propietario=request.user) to ensure a restaurant owner can only modify their own products.

Public Restaurant Pages

Restaurant Listing

GET /restaurantes/ renders a page that displays all restaurants where activo=True. Inactive restaurants are hidden from public view but still accessible to the owner through the panel.

Restaurant Detail Page

GET /restaurantes/restaurante/<pk>/ shows the full menu for a single restaurant. The view groups products by their category and passes the full list of ratings to calculate an average score:
def pagina_restaurante(request, pk):
    restaurante = get_object_or_404(Restaurante, pk=pk)
    categorias  = Categoria.objects.filter(restaurante=restaurante)
    calificaciones = Calificacion.objects.filter(pedido__restaurante=restaurante)
    promedio = round(
        sum(c.puntuacion for c in calificaciones) / calificaciones.count(), 1
    ) if calificaciones.count() > 0 else None
    ...
  • A customer can only leave one rating per delivered order (OneToOneField between Calificacion and Pedido).
  • The puede_calificar flag is set to True on the detail page only if the logged-in user has at least one entregado order from that restaurant that has not yet been rated.
  • Comments are checked against a curated list of Spanish profanity words using the better-profanity library. Flagged comments are rejected with an error message and the rating is not saved.

How Menus Are Structured

Restaurante
└── Categoria  (e.g. "Hamburguesas")
    └── Producto  (e.g. "Smash Burger - $18,000")
└── Categoria  (e.g. "Bebidas")
    └── Producto  (e.g. "Limonada de Coco - $6,000")
A product belongs to exactly one category, and a category belongs to exactly one restaurant. Deleting a category cascades to all its products. The restaurante_id of a product is always resolved through the chain producto → categoria → restaurante.

Build docs developers (and LLMs) love