"""Tipos compartidos, enumeraciones y objetos de valor embebidos. Estos componentes se reutilizan tanto en los modelos de dominio (:mod:`app.models`) como en los esquemas de la API (:mod:`app.schemas`), manteniendo el sistema genérico y fácil de extender. """ from __future__ import annotations from datetime import datetime, timezone from enum import Enum from typing import Annotated from pydantic import BaseModel, BeforeValidator, ConfigDict, EmailStr, Field # --------------------------------------------------------------------------- # Identificadores # --------------------------------------------------------------------------- # El ``_id`` de MongoDB (ObjectId) se serializa siempre como cadena. PyObjectId = Annotated[str, BeforeValidator(str)] # Patrón de hora en formato 24h "HH:MM". _HORA_PATTERN = r"^([01]\d|2[0-3]):[0-5]\d$" def utcnow() -> datetime: """Devuelve la fecha y hora actual en UTC (con zona horaria).""" return datetime.now(timezone.utc) # --------------------------------------------------------------------------- # Enumeraciones # --------------------------------------------------------------------------- class TipoItem(str, Enum): """Tipo de item ofertado por un negocio.""" menu = "menu" servicio = "servicio" producto = "producto" class DiaSemana(str, Enum): """Días de la semana para los horarios de atención.""" lunes = "lunes" martes = "martes" miercoles = "miercoles" jueves = "jueves" viernes = "viernes" sabado = "sabado" domingo = "domingo" # --------------------------------------------------------------------------- # Objetos de valor embebidos # --------------------------------------------------------------------------- class Telefono(BaseModel): """Número de teléfono de un negocio.""" tipo: str = Field(default="principal", max_length=40) numero: str = Field(min_length=1, max_length=40) class Correo(BaseModel): """Correo electrónico de contacto de un negocio.""" tipo: str = Field(default="principal", max_length=40) correo: EmailStr class RedSocial(BaseModel): """Enlace a una red social del negocio.""" tipo: str = Field(min_length=1, max_length=40) url: str = Field(min_length=1, max_length=2048) class Direccion(BaseModel): """Dirección física del negocio.""" direccion: str = Field(min_length=1, max_length=512) referencias: str | None = Field(default=None, max_length=512) class Ubicacion(BaseModel): """Coordenadas geográficas del negocio.""" latitud: float = Field(ge=-90, le=90) longitud: float = Field(ge=-180, le=180) class Horario(BaseModel): """Horario de atención para un día concreto.""" dia: DiaSemana apertura: str = Field(pattern=_HORA_PATTERN) cierre: str = Field(pattern=_HORA_PATTERN) class Imagen(BaseModel): """Imagen asociada a un negocio.""" url: str = Field(min_length=1, max_length=2048) principal: bool = False class ImagenItem(BaseModel): """Imagen asociada a un item.""" url: str = Field(min_length=1, max_length=2048) # --------------------------------------------------------------------------- # Modelo base de documentos # --------------------------------------------------------------------------- class MongoModel(BaseModel): """Base para los modelos que representan documentos de MongoDB. Mapea el ``_id`` de Mongo al campo ``id`` y lo expone como cadena. """ model_config = ConfigDict( populate_by_name=True, use_enum_values=True, arbitrary_types_allowed=True, json_schema_extra={"description": "Documento almacenado en MongoDB."}, ) # Lee el ``_id`` de Mongo y lo expone como ``id`` (cadena) en la salida. id: PyObjectId | None = Field( default=None, alias="_id", serialization_alias="id" )