[
  {
    "path": ".gitignore",
    "content": "__pycache__/\n*.py[cod]"
  },
  {
    "path": "Backend/FastAPI/db/client.py",
    "content": "# Clase en vídeo: https://youtu.be/_y9qQZXE24A?t=20480)\n\n### MongoDB client ###\n\n# Descarga versión community: https://www.mongodb.com/try/download\n# Instalación:https://www.mongodb.com/docs/manual/tutorial\n# Módulo conexión MongoDB: pip install pymongo\n# Ejecución: sudo mongod --dbpath \"/path/a/la/base/de/datos/\"\n# Conexión: mongodb://localhost\n\nfrom pymongo import MongoClient\n\n# Descomentar el db_client local o remoto correspondiente\n\n# Base de datos local MongoDB\ndb_client = MongoClient().local\n\n# Clase en vídeo: https://youtu.be/_y9qQZXE24A?t=25470\n\n# Base de datos remota MongoDB Atlas (https://mongodb.com)\n# db_client = MongoClient(\n#     \"mongodb+srv://<user>:<password>@<url>/?retryWrites=true&w=majority\").test\n\n# Despliegue API en la nube:\n# Deta (deprecado) - https://www.deta.sh/\n# Vercel - https://www.vercel.com\n# Instrucciones - https://cleverzone.medium.com/fastapi-deployment-into-vercel-0fa4e6478014\n# MUY IMPORTANTE - Al desplegar en producción, preparar el proyecto para trabajar con variables de entorno que hagan referencia a datos sensibles:\n# - Nunca subas a un repositorio público el valor de las variables\n# - Puedes usar dotenv en Python\n# - Añade el valor de las variables desde el proveedor de hosting\n"
  },
  {
    "path": "Backend/FastAPI/db/models/user.py",
    "content": "# Clase en vídeo: https://youtu.be/_y9qQZXE24A?t=20480\n\n### User model ###\n\nfrom pydantic import BaseModel\nfrom typing import Optional\n\n\nclass User(BaseModel):\n    id: Optional[str] = None\n    username: str\n    email: str\n"
  },
  {
    "path": "Backend/FastAPI/db/schemas/user.py",
    "content": "# Clase en vídeo (22/12/2022): https://www.twitch.tv/videos/1686104006\n\n### User schema ###\n\ndef user_schema(user) -> dict:\n    return {\"id\": str(user[\"_id\"]),\n            \"username\": user[\"username\"],\n            \"email\": user[\"email\"]}\n\n\ndef users_schema(users) -> list:\n    return [user_schema(user) for user in users]\n"
  },
  {
    "path": "Backend/FastAPI/main.py",
    "content": "# Clase en vídeo: https://youtu.be/_y9qQZXE24A\n\n### Hola Mundo ###\n\n# Documentación oficial: https://fastapi.tiangolo.com/es/\n\n# Instala FastAPI: pip install \"fastapi[all]\"\n\nfrom fastapi import FastAPI\nfrom routers import products, users, basic_auth_users, jwt_auth_users, users_db\nfrom fastapi.staticfiles import StaticFiles\nimport os\n\napp = FastAPI()\n\n# Clase en vídeo: https://youtu.be/_y9qQZXE24A?t=12475\napp.include_router(products.router)\napp.include_router(users.router)\n\n# Clase en vídeo: https://youtu.be/_y9qQZXE24A?t=14094\napp.include_router(basic_auth_users.router)\n\n# Clase en vídeo: https://youtu.be/_y9qQZXE24A?t=17664\napp.include_router(jwt_auth_users.router)\n\n# Clase en vídeo: https://youtu.be/_y9qQZXE24A?t=20480\napp.include_router(users_db.router)\n\n# Clase en vídeo: https://youtu.be/_y9qQZXE24A?t=13618\napp.mount(\"/static\", StaticFiles(directory=\"static\"), name=\"static\")\n\n\n# Url local: http://127.0.0.1:8000\n\n\n@app.get(\"/\")\nasync def root():\n    return \"Hola FastAPI!\"\n\n# Url local: http://127.0.0.1:8000/url\n\n\n@app.get(\"/url\")\nasync def url():\n    return {\"url\": \"https://mouredev.com/python\"}\n\n# Inicia el server: uvicorn main:app --reload\n# Detener el server: CTRL+C\n\n# Documentación con Swagger: http://127.0.0.1:8000/docs\n# Documentación con Redocly: http://127.0.0.1:8000/redoc\n"
  },
  {
    "path": "Backend/FastAPI/requirements.txt",
    "content": "fastapi[standard]\npython-jose\npasslib\nbcrypt\npymongo"
  },
  {
    "path": "Backend/FastAPI/routers/basic_auth_users.py",
    "content": "# Clase en vídeo: https://youtu.be/_y9qQZXE24A?t=14094\n\n### Users API con autorización OAuth2 básica ###\n\nfrom fastapi import APIRouter, Depends, HTTPException, status\nfrom pydantic import BaseModel\nfrom fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm\n\nrouter = APIRouter(\n    prefix=\"/basicauth\",\n    tags=[\"basicauth\"],\n    responses={status.HTTP_404_NOT_FOUND: {\"message\": \"No encontrado\"}}\n)\n\noauth2 = OAuth2PasswordBearer(tokenUrl=\"login\")\n\n\nclass User(BaseModel):\n    username: str\n    full_name: str\n    email: str\n    disabled: bool\n\n\nclass UserDB(User):\n    password: str\n\n\nusers_db = {\n    \"mouredev\": {\n        \"username\": \"mouredev\",\n        \"full_name\": \"Brais Moure\",\n        \"email\": \"braismoure@mouredev.com\",\n        \"disabled\": False,\n        \"password\": \"123456\"\n    },\n    \"mouredev2\": {\n        \"username\": \"mouredev2\",\n        \"full_name\": \"Brais Moure 2\",\n        \"email\": \"braismoure2@mouredev.com\",\n        \"disabled\": True,\n        \"password\": \"654321\"\n    }\n}\n\n\ndef search_user_db(username: str):\n    if username in users_db:\n        return UserDB(**users_db[username])\n\n\ndef search_user(username: str):\n    if username in users_db:\n        return User(**users_db[username])\n\n\nasync def current_user(token: str = Depends(oauth2)):\n    user = search_user(token)\n    if not user:\n        raise HTTPException(\n            status_code=status.HTTP_401_UNAUTHORIZED,\n            detail=\"Credenciales de autenticación inválidas\",\n            headers={\"WWW-Authenticate\": \"Bearer\"})\n\n    if user.disabled:\n        raise HTTPException(\n            status_code=status.HTTP_400_BAD_REQUEST,\n            detail=\"Usuario inactivo\")\n\n    return user\n\n\n@router.post(\"/login\")\nasync def login(form: OAuth2PasswordRequestForm = Depends()):\n    user_db = users_db.get(form.username)\n    if not user_db:\n        raise HTTPException(\n            status_code=status.HTTP_400_BAD_REQUEST, detail=\"El usuario no es correcto\")\n\n    user = search_user_db(form.username)\n    if not form.password == user.password:\n        raise HTTPException(\n            status_code=status.HTTP_400_BAD_REQUEST, detail=\"La contraseña no es correcta\")\n\n    return {\"access_token\": user.username, \"token_type\": \"bearer\"}\n\n\n@router.get(\"/users/me\")\nasync def me(user: User = Depends(current_user)):\n    return user\n"
  },
  {
    "path": "Backend/FastAPI/routers/jwt_auth_users.py",
    "content": "# Clase en vídeo: https://youtu.be/_y9qQZXE24A?t=17664\n\n### Users API con autorización OAuth2 JWT ###\n\nfrom fastapi import APIRouter, Depends, HTTPException, status\nfrom pydantic import BaseModel\nfrom fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm\nfrom jose import jwt, JWTError\nfrom passlib.context import CryptContext\nfrom datetime import datetime, timedelta, timezone\n\nALGORITHM = \"HS256\"\nACCESS_TOKEN_DURATION = 1\nSECRET = \"201d573bd7d1344d3a3bfce1550b69102fd11be3db6d379508b6cccc58ea230b\"\n\nrouter = APIRouter(\n    prefix=\"/jwtauth\",\n    tags=[\"jwtauth\"],\n    responses={status.HTTP_404_NOT_FOUND: {\"message\": \"No encontrado\"}}\n)\n\noauth2 = OAuth2PasswordBearer(tokenUrl=\"login\")\n\ncrypt = CryptContext(schemes=[\"bcrypt\"])\n\n\nclass User(BaseModel):\n    username: str\n    full_name: str\n    email: str\n    disabled: bool\n\n\nclass UserDB(User):\n    password: str\n\n\nusers_db = {\n    \"mouredev\": {\n        \"username\": \"mouredev\",\n        \"full_name\": \"Brais Moure\",\n        \"email\": \"braismoure@mouredev.com\",\n        \"disabled\": False,\n        \"password\": \"$2a$12$B2Gq.Dps1WYf2t57eiIKjO4DXC3IUMUXISJF62bSRiFfqMdOI2Xa6\"\n    },\n    \"mouredev2\": {\n        \"username\": \"mouredev2\",\n        \"full_name\": \"Brais Moure 2\",\n        \"email\": \"braismoure2@mouredev.com\",\n        \"disabled\": True,\n        \"password\": \"$2a$12$SduE7dE.i3/ygwd0Kol8bOFvEABaoOOlC8JsCSr6wpwB4zl5STU4S\"\n    }\n}\n\n\ndef search_user_db(username: str):\n    if username in users_db:\n        return UserDB(**users_db[username])\n\n\ndef search_user(username: str):\n    if username in users_db:\n        return User(**users_db[username])\n\n\nasync def auth_user(token: str = Depends(oauth2)):\n\n    exception = HTTPException(\n        status_code=status.HTTP_401_UNAUTHORIZED,\n        detail=\"Credenciales de autenticación inválidas\",\n        headers={\"WWW-Authenticate\": \"Bearer\"})\n\n    try:\n        username = jwt.decode(token, SECRET, algorithms=[ALGORITHM]).get(\"sub\")\n        if username is None:\n            raise exception\n\n    except JWTError:\n        raise exception\n\n    return search_user(username)\n\n\nasync def current_user(user: User = Depends(auth_user)):\n    if user.disabled:\n        raise HTTPException(\n            status_code=status.HTTP_400_BAD_REQUEST,\n            detail=\"Usuario inactivo\")\n\n    return user\n\n\n@router.post(\"/login\")\nasync def login(form: OAuth2PasswordRequestForm = Depends()):\n\n    user_db = users_db.get(form.username)\n    if not user_db:\n        raise HTTPException(\n            status_code=status.HTTP_400_BAD_REQUEST, detail=\"El usuario no es correcto\")\n\n    user = search_user_db(form.username)\n\n    if not crypt.verify(form.password, user.password):\n        raise HTTPException(\n            status_code=status.HTTP_400_BAD_REQUEST, detail=\"La contraseña no es correcta\")\n\n    access_token = {\"sub\": user.username,\n                    \"exp\": datetime.now(timezone.utc) + timedelta(minutes=ACCESS_TOKEN_DURATION)}\n\n    return {\"access_token\": jwt.encode(access_token, SECRET, algorithm=ALGORITHM), \"token_type\": \"bearer\"}\n\n\n@router.get(\"/users/me\")\nasync def me(user: User = Depends(current_user)):\n    return user\n"
  },
  {
    "path": "Backend/FastAPI/routers/products.py",
    "content": "# Clase en vídeo: https://youtu.be/_y9qQZXE24A?t=12475\n\n### Products API ###\n\nfrom fastapi import APIRouter\n\nrouter = APIRouter(\n    prefix=\"/products\",\n    tags=[\"products\"],\n    responses={404: {\"message\": \"No encontrado\"}}\n)\n\nproducts_list = [\"Producto 1\", \"Producto 2\", \"Producto 3\", \"Producto 4\", \"Producto 5\"]\n\n\n@router.get(\"/\")\nasync def products():\n    return products_list\n\n\n@router.get(\"/{id}\")\nasync def products(id: int):\n    return products_list[id]\n"
  },
  {
    "path": "Backend/FastAPI/routers/users.py",
    "content": "# Clase en vídeo: https://youtu.be/_y9qQZXE24A?t=5382\n\n### Users API ###\n\nfrom fastapi import APIRouter, HTTPException\nfrom pydantic import BaseModel\n\n# Inicia el server: uvicorn users:app --reload\n\nrouter = APIRouter()\n\n\nclass User(BaseModel):\n    id: int\n    name: str\n    surname: str\n    url: str\n    age: int\n\n\nusers_list = [User(id=1, name=\"Brais\", surname=\"Moure\", url=\"https://moure.dev\", age=35),\n              User(id=2, name=\"Moure\", surname=\"Dev\",\n                   url=\"https://mouredev.com\", age=35),\n              User(id=3, name=\"Brais\", surname=\"Dahlberg\", url=\"https://haakon.com\", age=33)]\n\n\n@router.get(\"/usersjson\")\nasync def usersjson():  # Creamos un JSON a mano\n    return [{\"name\": \"Brais\", \"surname\": \"Moure\", \"url\": \"https://moure.dev\", \"age\": 35},\n            {\"name\": \"Moure\", \"surname\": \"Dev\",\n                \"url\": \"https://mouredev.com\", \"age\": 35},\n            {\"name\": \"Haakon\", \"surname\": \"Dahlberg\", \"url\": \"https://haakon.com\", \"age\": 33}]\n\n\n@router.get(\"/users\")\nasync def users():\n    return users_list\n\n\n@router.get(\"/user/{id}\")  # Path\nasync def user(id: int):\n    return search_user(id)\n\n\n@router.get(\"/user/\")  # Query\nasync def user(id: int):\n    return search_user(id)\n\n\n# Clase en vídeo: https://youtu.be/_y9qQZXE24A?t=8529\n\n\n@router.post(\"/user/\", response_model=User, status_code=201)\nasync def user(user: User):\n    if type(search_user(user.id)) == User:\n        raise HTTPException(status_code=404, detail=\"El usuario ya existe\")\n\n    users_list.append(user)\n    return user\n\n\n@router.put(\"/user/\")\nasync def user(user: User):\n\n    found = False\n\n    for index, saved_user in enumerate(users_list):\n        if saved_user.id == user.id:\n            users_list[index] = user\n            found = True\n\n    if not found:\n        return {\"error\": \"No se ha actualizado el usuario\"}\n\n    return user\n\n\n@router.delete(\"/user/{id}\")\nasync def user(id: int):\n\n    found = False\n\n    for index, saved_user in enumerate(users_list):\n        if saved_user.id == id:\n            del users_list[index]\n            found = True\n\n    if not found:\n        return {\"error\": \"No se ha eliminado el usuario\"}\n\n\ndef search_user(id: int):\n    users = filter(lambda user: user.id == id, users_list)\n    try:\n        return list(users)[0]\n    except:\n        return {\"error\": \"No se ha encontrado el usuario\"}\n"
  },
  {
    "path": "Backend/FastAPI/routers/users_db.py",
    "content": "# Clase en vídeo: https://youtu.be/_y9qQZXE24A?t=20480\n\n### Users DB API ###\n\nfrom fastapi import APIRouter, HTTPException, status\nfrom db.models.user import User\nfrom db.schemas.user import user_schema, users_schema\nfrom db.client import db_client\nfrom bson import ObjectId\n\nrouter = APIRouter(\n    prefix=\"/userdb\",\n    tags=[\"userdb\"],\n    responses={status.HTTP_404_NOT_FOUND: {\"message\": \"No encontrado\"}})\n\n\n@router.get(\"/\", response_model=list[User])\nasync def users():\n    return users_schema(db_client.users.find())\n\n\n@router.get(\"/{id}\")  # Path\nasync def user(id: str):\n    return search_user(\"_id\", ObjectId(id))\n\n\n@router.get(\"/\")  # Query\nasync def user(id: str):\n    return search_user(\"_id\", ObjectId(id))\n\n\n@router.post(\"/\", response_model=User, status_code=status.HTTP_201_CREATED)\nasync def user(user: User):\n    if type(search_user(\"email\", user.email)) == User:\n        raise HTTPException(\n            status_code=status.HTTP_404_NOT_FOUND, detail=\"El usuario ya existe\")\n\n    user_dict = dict(user)\n    del user_dict[\"id\"]\n\n    id = db_client.users.insert_one(user_dict).inserted_id\n\n    new_user = user_schema(db_client.users.find_one({\"_id\": id}))\n\n    return User(**new_user)\n\n\n@router.put(\"/\", response_model=User)\nasync def user(user: User):\n\n    user_dict = dict(user)\n    del user_dict[\"id\"]\n\n    try:\n        db_client.users.find_one_and_replace(\n            {\"_id\": ObjectId(user.id)}, user_dict)\n    except:\n        return {\"error\": \"No se ha actualizado el usuario\"}\n\n    return search_user(\"_id\", ObjectId(user.id))\n\n\n@router.delete(\"/{id}\", status_code=status.HTTP_204_NO_CONTENT)\nasync def user(id: str):\n\n    found = db_client.users.find_one_and_delete({\"_id\": ObjectId(id)})\n\n    if not found:\n        return {\"error\": \"No se ha eliminado el usuario\"}\n\n# Helper\n\n\ndef search_user(field: str, key):\n\n    try:\n        user = db_client.users.find_one({field: key})\n        return User(**user_schema(user))\n    except:\n        return {\"error\": \"No se ha encontrado el usuario\"}\n"
  },
  {
    "path": "Backend/FastAPI/vercel.json",
    "content": "{\n    \"builds\": [\n        {\n            \"src\": \"main.py\",\n            \"use\": \"@vercel/python\"\n        }\n    ],\n    \"routes\": [\n        {\n            \"src\": \"/(.*)\",\n            \"dest\": \"main.py\"\n        }\n    ]\n}"
  },
  {
    "path": "Backend/type_hints.py",
    "content": "# Clase en vídeo: https://youtu.be/_y9qQZXE24A?t=1810\n\n### Type Hints ###\n\nmy_string_variable = \"My String variable\"\nprint(my_string_variable)\nprint(type(my_string_variable))\n\nmy_string_variable = 5\nprint(my_string_variable)\nprint(type(my_string_variable))\n\nmy_typed_variable: str = \"My typed String variable\"\nprint(my_typed_variable)\nprint(type(my_typed_variable))\n\nmy_typed_variable = 5\nprint(my_typed_variable)\nprint(type(my_typed_variable))\n"
  },
  {
    "path": "Basic/00_helloworld.py",
    "content": "# Clase en vídeo: https://youtu.be/Kp4Mvapo5kc\n\n### Hola Mundo ###\n\n# Nuestro hola mundo en Python\nprint(\"Hola Python\")\nprint('Hola Python')\n\n# Esto es un comentario\n\n\"\"\"\nEste es un\ncomentario\nen varias líneas\n\"\"\"\n\n'''\nEste también es un\ncomentario\nen varias líneas\n'''\n\n# Cómo consultar el tipo de dato\nprint(type(\"Soy un dato str\"))  # Tipo 'str'\nprint(type(5))  # Tipo 'int'\nprint(type(1.5))  # Tipo 'float'\nprint(type(3 + 1j))  # Tipo 'complex'\nprint(type(True))  # Tipo 'bool'\nprint(type(print(\"Mi cadena de texto\")))  # Tipo 'NoneType'\n"
  },
  {
    "path": "Basic/01_variables.py",
    "content": "# Clase en vídeo: https://youtu.be/Kp4Mvapo5kc?t=2938\n\n### Variables ###\n\nmy_string_variable = \"My String variable\"\nprint(my_string_variable)\n\nmy_int_variable = 5\nprint(my_int_variable)\n\nmy_int_to_str_variable = str(my_int_variable)\nprint(my_int_to_str_variable)\nprint(type(my_int_to_str_variable))\n\nmy_bool_variable = False\nprint(my_bool_variable)\n\n# Concatenación de variables en un print\nprint(my_string_variable, my_int_to_str_variable, my_bool_variable)\nprint(\"Este es el valor de:\", my_bool_variable)\n\n# Algunas funciones del sistema\nprint(len(my_string_variable))\n\n# Variables en una sola línea. ¡Cuidado con abusar de esta sintaxis!\nname, surname, alias, age = \"Brais\", \"Moure\", 'MoureDev', 35\nprint(\"Me llamo:\", name, surname, \". Mi edad es:\",\n      age, \". Y mi alias es:\", alias)\n\n# Inputs\nname = input('¿Cuál es tu nombre? ')\nage = input('¿Cuántos años tienes? ')\nprint(name)\nprint(age)\n\n# Cambiamos su tipo\nname = 35\nage = \"Brais\"\nprint(name)\nprint(age)\n\n# ¿Forzamos el tipo?\naddress: str = \"Mi dirección\"\naddress = True\naddress = 5\naddress = 1.2\nprint(type(address))\n"
  },
  {
    "path": "Basic/02_operators.py",
    "content": "# Clase en vídeo: https://youtu.be/Kp4Mvapo5kc?t=5665\n\n### Operadores Aritméticos ###\n\n# Operaciones con enteros\nprint(3 + 4)\nprint(3 - 4)\nprint(3 * 4)\nprint(3 / 4)\nprint(10 % 3)\nprint(10 // 3)\nprint(2 ** 3)\nprint(2 ** 3 + 3 - 7 / 1 // 4)\n\n# Operaciones con cadenas de texto\nprint(\"Hola \" + \"Python \" + \"¿Qué tal?\")\nprint(\"Hola \" + str(5))\n\n# Operaciones mixtas\nprint(\"Hola \" * 5)\nprint(\"Hola \" * (2 ** 3))\n\nmy_float = 2.5 * 2\nprint(\"Hola \" * int(my_float))\n\n### Operadores Comparativos ###\n\n# Operaciones con enteros\nprint(3 > 4)\nprint(3 < 4)\nprint(3 >= 4)\nprint(4 <= 4)\nprint(3 == 4)\nprint(3 != 4)\n\n# Operaciones con cadenas de texto\nprint(\"Hola\" > \"Python\")\nprint(\"Hola\" < \"Python\")\nprint(\"aaaa\" >= \"abaa\")  # Ordenación alfabética por ASCII\nprint(len(\"aaaa\") >= len(\"abaa\"))  # Cuenta caracteres\nprint(\"Hola\" <= \"Python\")\nprint(\"Hola\" == \"Hola\")\nprint(\"Hola\" != \"Python\")\n\n### Operadores Lógicos ###\n\n# Basada en el Álgebra de Boole https://es.wikipedia.org/wiki/%C3%81lgebra_de_Boole\nprint(3 > 4 and \"Hola\" > \"Python\")\nprint(3 > 4 or \"Hola\" > \"Python\")\nprint(3 < 4 and \"Hola\" < \"Python\")\nprint(3 < 4 or \"Hola\" > \"Python\")\nprint(3 < 4 or (\"Hola\" > \"Python\" and 4 == 4))\nprint(not (3 > 4))\n"
  },
  {
    "path": "Basic/03_strings.py",
    "content": "# Clase en vídeo: https://youtu.be/Kp4Mvapo5kc?t=8643\n\n### Strings ###\n\nmy_string = \"Mi String\"\nmy_other_string = 'Mi otro String'\n\nprint(len(my_string))\nprint(len(my_other_string))\nprint(my_string + \" \" + my_other_string)\n\nmy_new_line_string = \"Este es un String\\ncon salto de línea\"\nprint(my_new_line_string)\n\nmy_tab_string = \"\\tEste es un String con tabulación\"\nprint(my_tab_string)\n\nmy_scape_string = \"\\\\tEste es un String \\\\n escapado\"\nprint(my_scape_string)\n\n# Formateo\n\nname, surname, age = \"Brais\", \"Moure\", 35\nprint(\"Mi nombre es {} {} y mi edad es {}\".format(name, surname, age))\nprint(\"Mi nombre es %s %s y mi edad es %d\" % (name, surname, age))\nprint(\"Mi nombre es \" + name + \" \" + surname + \" y mi edad es \" + str(age))\nprint(f\"Mi nombre es {name} {surname} y mi edad es {age}\")\n\n# Desempaqueado de caracteres\n\nlanguage = \"python\"\na, b, c, d, e, f = language\nprint(a)\nprint(e)\n\n# División\n\nlanguage_slice = language[1:3]\nprint(language_slice)\n\nlanguage_slice = language[1:]\nprint(language_slice)\n\nlanguage_slice = language[-2]\nprint(language_slice)\n\nlanguage_slice = language[0:6:2]\nprint(language_slice)\n\n# Reverse\n\nreversed_language = language[::-1]\nprint(reversed_language)\n\n# Funciones del lenguaje\n\nprint(language.capitalize())\nprint(language.upper())\nprint(language.count(\"t\"))\nprint(language.isnumeric())\nprint(\"1\".isnumeric())\nprint(language.lower())\nprint(language.lower().isupper())\nprint(language.startswith(\"Py\"))\nprint(\"Py\" == \"py\")  # No es lo mismo\n"
  },
  {
    "path": "Basic/04_lists.py",
    "content": "# Clase en vídeo: https://youtu.be/Kp4Mvapo5kc?t=10872\n\n### Lists ###\n\n# Definición\n\nmy_list = list()\nmy_other_list = []\n\nprint(len(my_list))\n\nmy_list = [35, 24, 62, 52, 30, 30, 17]\n\nprint(my_list)\nprint(len(my_list))\n\nmy_other_list = [35, 1.77, \"Brais\", \"Moure\"]\n\nprint(type(my_list))\nprint(type(my_other_list))\n\n# Acceso a elementos y búsqueda\n\nprint(my_other_list[0])\nprint(my_other_list[1])\nprint(my_other_list[-1])\nprint(my_other_list[-4])\nprint(my_list.count(30))\n# print(my_other_list[4]) IndexError\n# print(my_other_list[-5]) IndexError\n\nprint(my_other_list.index(\"Brais\"))\n\nage, height, name, surname = my_other_list\nprint(name)\n\nname, height, age, surname = my_other_list[2], my_other_list[1], my_other_list[0], my_other_list[3]\nprint(age)\n\n# Concatenación\n\nprint(my_list + my_other_list)\n#print(my_list - my_other_list)\n\n# Creación, inserción, actualización y eliminación\n\nmy_other_list.append(\"MoureDev\")\nprint(my_other_list)\n\nmy_other_list.insert(1, \"Rojo\")\nprint(my_other_list)\n\nmy_other_list[1] = \"Azul\"\nprint(my_other_list)\n\nmy_other_list.remove(\"Azul\")\nprint(my_other_list)\n\nmy_list.remove(30)\nprint(my_list)\n\nprint(my_list.pop())\nprint(my_list)\n\nmy_pop_element = my_list.pop(2)\nprint(my_pop_element)\nprint(my_list)\n\ndel my_list[2]\nprint(my_list)\n\n# Operaciones con listas\n\nmy_new_list = my_list.copy()\n\nmy_list.clear()\nprint(my_list)\nprint(my_new_list)\n\nmy_new_list.reverse()\nprint(my_new_list)\n\nmy_new_list.sort()\nprint(my_new_list)\n\n# Sublistas\n\nprint(my_new_list[1:3])\n\n# Cambio de tipo\n\nmy_list = \"Hola Python\"\nprint(my_list)\nprint(type(my_list))\n"
  },
  {
    "path": "Basic/05_tuples.py",
    "content": "# Clase en vídeo: https://youtu.be/Kp4Mvapo5kc?t=14711\n\n### Tuples ###\n\n# Definición\n\nmy_tuple = tuple()\nmy_other_tuple = ()\n\nmy_tuple = (35, 1.77, \"Brais\", \"Moure\", \"Brais\")\nmy_other_tuple = (35, 60, 30)\n\nprint(my_tuple)\nprint(type(my_tuple))\n\n# Acceso a elementos y búsqueda\n\nprint(my_tuple[0])\nprint(my_tuple[-1])\n# print(my_tuple[4]) IndexError\n# print(my_tuple[-6]) IndexError\n\nprint(my_tuple.count(\"Brais\"))\nprint(my_tuple.index(\"Moure\"))\nprint(my_tuple.index(\"Brais\"))\n\n# my_tuple[1] = 1.80 'tuple' object does not support item assignment\n\n# Concatenación\n\nmy_sum_tuple = my_tuple + my_other_tuple\nprint(my_sum_tuple)\n\n# Subtuplas\n\nprint(my_sum_tuple[3:6])\n\n# Tupla mutable con conversión a lista\n\nmy_tuple = list(my_tuple)\nprint(type(my_tuple))\n\nmy_tuple[4] = \"MoureDev\"\nmy_tuple.insert(1, \"Azul\")\nmy_tuple = tuple(my_tuple)\nprint(my_tuple)\nprint(type(my_tuple))\n\n# Eliminación\n\n# del my_tuple[2] TypeError: 'tuple' object doesn't support item deletion\n\ndel my_tuple\n# print(my_tuple) NameError: name 'my_tuple' is not defined\n"
  },
  {
    "path": "Basic/06_sets.py",
    "content": "# Clase en vídeo: https://youtu.be/Kp4Mvapo5kc?t=16335\n\n### Sets ###\n\n# Definición\n\nmy_set = set()\nmy_other_set = {}\n\nprint(type(my_set))\nprint(type(my_other_set))  # Inicialmente es un diccionario\n\nmy_other_set = {\"Brais\", \"Moure\", 35}\nprint(type(my_other_set))\n\nprint(len(my_other_set))\n\n# Inserción\n\nmy_other_set.add(\"MoureDev\")\n\nprint(my_other_set)  # Un set no es una estructura ordenada\n\nmy_other_set.add(\"MoureDev\")  # Un set no admite repetidos\n\nprint(my_other_set)\n\n# Búsqueda\n\nprint(\"Moure\" in my_other_set)\nprint(\"Mouri\" in my_other_set)\n\n# Eliminación\n\nmy_other_set.remove(\"Moure\")\nprint(my_other_set)\n\nmy_other_set.clear()\nprint(len(my_other_set))\n\ndel my_other_set\n# print(my_other_set) NameError: name 'my_other_set' is not defined\n\n# Transformación\n\nmy_set = {\"Brais\", \"Moure\", 35}\nmy_list = list(my_set)\nprint(my_list)\nprint(my_list[0])\n\nmy_other_set = {\"Kotlin\", \"Swift\", \"Python\"}\n\n# Otras operaciones\n\nmy_new_set = my_set.union(my_other_set)\nprint(my_new_set.union(my_new_set).union(my_set).union({\"JavaScript\", \"C#\"}))\nprint(my_new_set.difference(my_set))\n"
  },
  {
    "path": "Basic/07_dicts.py",
    "content": "# Clase en vídeo: https://youtu.be/Kp4Mvapo5kc\n\n### Dictionaries ###\n\n# Definición\n\nmy_dict = dict()\nmy_other_dict = {}\n\nprint(type(my_dict))\nprint(type(my_other_dict))\n\nmy_other_dict = {\"Nombre\": \"Brais\",\n                 \"Apellido\": \"Moure\", \"Edad\": 35, 1: \"Python\"}\n\nmy_dict = {\n    \"Nombre\": \"Brais\",\n    \"Apellido\": \"Moure\",\n    \"Edad\": 35,\n    \"Lenguajes\": {\"Python\", \"Swift\", \"Kotlin\"},\n    1: 1.77\n}\n\nprint(my_other_dict)\nprint(my_dict)\n\nprint(len(my_other_dict))\nprint(len(my_dict))\n\n# Búsqueda\n\nprint(my_dict[1])\nprint(my_dict[\"Nombre\"])\n\nprint(\"Moure\" in my_dict)\nprint(\"Apellido\" in my_dict)\n\n# Inserción\n\nmy_dict[\"Calle\"] = \"Calle MoureDev\"\nprint(my_dict)\n\n# Actualización\n\nmy_dict[\"Nombre\"] = \"Pedro\"\nprint(my_dict[\"Nombre\"])\n\n# Eliminación\n\ndel my_dict[\"Calle\"]\nprint(my_dict)\n\n# Otras operaciones\n\nprint(my_dict.items())\nprint(my_dict.keys())\nprint(my_dict.values())\n\nmy_list = [\"Nombre\", 1, \"Piso\"]\n\nmy_new_dict = dict.fromkeys((my_list))\nprint(my_new_dict)\nmy_new_dict = dict.fromkeys((\"Nombre\", 1, \"Piso\"))\nprint((my_new_dict))\nmy_new_dict = dict.fromkeys(my_dict)\nprint((my_new_dict))\nmy_new_dict = dict.fromkeys(my_dict, \"MoureDev\")\nprint((my_new_dict))\n\nmy_values = my_new_dict.values()\nprint(type(my_values))\n\nprint(my_new_dict.values())\nprint(list(dict.fromkeys(list(my_new_dict.values())).keys()))\nprint(tuple(my_new_dict))\nprint(set(my_new_dict))\n"
  },
  {
    "path": "Basic/08_conditionals.py",
    "content": "# Clase en vídeo: https://youtu.be/Kp4Mvapo5kc?t=21442\n\n### Conditionals ###\n\n# if\n\nmy_condition = False\n\nif my_condition:  # Es lo mismo que if my_condition == True:\n    print(\"Se ejecuta la condición del if\")\n\nmy_condition = 5 * 5\n\nif my_condition == 10:\n    print(\"Se ejecuta la condición del segundo if\")\n\n# if, elif, else\n\nif my_condition > 10 and my_condition < 20:\n    print(\"Es mayor que 10 y menor que 20\")\nelif my_condition == 25:\n    print(\"Es igual a 25\")\nelse:\n    print(\"Es menor o igual que 10 o mayor o igual que 20 o distinto de 25\")\n\nprint(\"La ejecución continúa\")\n\n# Condicional con ispección de valor\n\nmy_string = \"\"\n\nif not my_string:\n    print(\"Mi cadena de texto es vacía\")\n\nif my_string == \"Mi cadena de textoooooo\":\n    print(\"Estas cadenas de texto coinciden\")\n"
  },
  {
    "path": "Basic/09_loops.py",
    "content": "# Clase en vídeo: https://youtu.be/Kp4Mvapo5kc?t=23822\n\n### Loops ###\n\n# While\n\nmy_condition = 0\n\nwhile my_condition < 10:\n    print(my_condition)\n    my_condition += 2\nelse:  # Es opcional\n    print(\"Mi condición es mayor o igual que 10\")\n\nprint(\"La ejecución continúa\")\n\nwhile my_condition < 20:\n    my_condition += 1\n    if my_condition == 15:\n        print(\"Se detiene la ejecución\")\n        break\n    print(my_condition)\n\nprint(\"La ejecución continúa\")\n\n# For\n\nmy_list = [35, 24, 62, 52, 30, 30, 17]\n\nfor element in my_list:\n    print(element)\n\nmy_tuple = (35, 1.77, \"Brais\", \"Moure\", \"Brais\")\n\nfor element in my_tuple:\n    print(element)\n\nmy_set = {\"Brais\", \"Moure\", 35}\n\nfor element in my_set:\n    print(element)\n\nmy_dict = {\"Nombre\": \"Brais\", \"Apellido\": \"Moure\", \"Edad\": 35, 1: \"Python\"}\n\nfor element in my_dict:\n    print(element)\n    if element == \"Edad\":\n        break\nelse:\n    print(\"El bucle for para el diccionario ha finalizado\")\n\nprint(\"La ejecución continúa\")\n\nfor element in my_dict:\n    print(element)\n    if element == \"Edad\":\n        continue\n    print(\"Se ejecuta\")\nelse:\n    print(\"El bluce for para diccionario ha finalizado\")\n"
  },
  {
    "path": "Basic/10_functions.py",
    "content": "# Clase en vídeo: https://youtu.be/Kp4Mvapo5kc?t=26619\n\n### Functions ###\n\n# Definición\n\ndef my_function():\n    print(\"Esto es una función\")\n\n\nmy_function()\nmy_function()\nmy_function()\n\n# Función con parámetros de entrada/argumentos\n\n\ndef sum_two_values(first_value: int, second_value):\n    print(first_value + second_value)\n\n\nsum_two_values(5, 7)\nsum_two_values(54754, 71231)\nsum_two_values(\"5\", \"7\")\nsum_two_values(1.4, 5.2)\n\n# Función con parámetros de entrada/argumentos y retorno\n\n\ndef sum_two_values_with_return(first_value, second_value):\n    my_sum = first_value + second_value\n    return my_sum\n\n\nmy_result = sum_two_values(1.4, 5.2)\nprint(my_result)\n\nmy_result = sum_two_values_with_return(10, 5)\nprint(my_result)\n\n# Función con parámetros de entrada/argumentos por clave\n\n\ndef print_name(name, surname):\n    print(f\"{name} {surname}\")\n\n\nprint_name(surname=\"Moure\", name=\"Brais\")\n\n# Función con parámetros de entrada/argumentos por defecto\n\n\ndef print_name_with_default(name, surname, alias=\"Sin alias\"):\n    print(f\"{name} {surname} {alias}\")\n\n\nprint_name_with_default(\"Brais\", \"Moure\")\nprint_name_with_default(\"Brais\", \"Moure\", \"MoureDev\")\n\n# Función con parámetros de entrada/argumentos arbitrarios\n\n\ndef print_upper_texts(*texts):\n    print(type(texts))\n    for text in texts:\n        print(text.upper())\n\n\nprint_upper_texts(\"Hola\", \"Python\", \"MoureDev\")\nprint_upper_texts(\"Hola\")\n"
  },
  {
    "path": "Basic/11_classes.py",
    "content": "# Clase en vídeo: https://youtu.be/Kp4Mvapo5kc?t=29327\n\n### Classes ###\n\n# Definición\n\nclass MyEmptyPerson:\n    pass  # Para poder dejar la clase vacía\n\n\nprint(MyEmptyPerson)\nprint(MyEmptyPerson())\n\n# Clase con constructor, funciones y propiedades privadas y públicas\n\n\nclass Person:\n    def __init__(self, name, surname, alias=\"Sin alias\"):\n        self.full_name = f\"{name} {surname} ({alias})\"  # Propiedad pública\n        self.__name = name  # Propiedad privada\n\n    def get_name(self):\n        return self.__name\n\n    def walk(self):\n        print(f\"{self.full_name} está caminando\")\n\n\nmy_person = Person(\"Brais\", \"Moure\")\nprint(my_person.full_name)\nprint(my_person.get_name())\nmy_person.walk()\n\nmy_other_person = Person(\"Brais\", \"Moure\", \"MoureDev\")\nprint(my_other_person.full_name)\nmy_other_person.walk()\nmy_other_person.full_name = \"Héctor de León (El loco de los perros)\"\nprint(my_other_person.full_name)\n\nmy_other_person.full_name = 666\nprint(my_other_person.full_name)\n"
  },
  {
    "path": "Basic/12_exceptions.py",
    "content": "# Clase en vídeo: https://youtu.be/Kp4Mvapo5kc?t=32030\n\n### Exception Handling ###\n\nnumberOne = 5\nnumberTwo = 1\nnumberTwo = \"1\"\n\n# Excepción base: try except\n\ntry:\n    print(numberOne + numberTwo)\n    print(\"No se ha producido un error\")\nexcept:\n    # Se ejecuta si se produce una excepción\n    print(\"Se ha producido un error\")\n\n# Flujo completo de una excepción: try except else finally\n\ntry:\n    print(numberOne + numberTwo)\n    print(\"No se ha producido un error\")\nexcept:\n    print(\"Se ha producido un error\")\nelse:  # Opcional\n    # Se ejecuta si no se produce una excepción\n    print(\"La ejecución continúa correctamente\")\nfinally:  # Opcional\n    # Se ejecuta siempre\n    print(\"La ejecución continúa\")\n\n# Excepciones por tipo\n\ntry:\n    print(numberOne + numberTwo)\n    print(\"No se ha producido un error\")\nexcept ValueError:\n    print(\"Se ha producido un ValueError\")\nexcept TypeError:\n    print(\"Se ha producido un TypeError\")\n\n# Captura de la información de la excepción\n\ntry:\n    print(numberOne + numberTwo)\n    print(\"No se ha producido un error\")\nexcept ValueError as error:\n    print(error)\nexcept Exception as my_random_error_name:\n    print(my_random_error_name)\n"
  },
  {
    "path": "Basic/13_modules.py",
    "content": "# Clase en vídeo: https://youtu.be/Kp4Mvapo5kc?t=34583\n\n### Modules ###\n\nfrom math import pi as PI_VALUE\nimport math\nfrom my_module import sumValue, printValue\nimport my_module\n\nmy_module.sumValue(5, 3, 1)\nmy_module.printValue(\"Hola Python!\")\n\n\nsumValue(5, 3, 1)\nprintValue(\"Hola python\")\n\n\nprint(math.pi)\nprint(math.pow(2, 8))\n\n\nprint(PI_VALUE)\n"
  },
  {
    "path": "Basic/my_module.py",
    "content": "# Clase en vídeo: https://youtu.be/Kp4Mvapo5kc?t=34583\n\n### Módulo para pruebas ###\n\ndef sumValue(numberOne, numberTwo, numberThree):\n    print(numberOne + numberTwo + numberThree)\n\n\ndef printValue(value):\n    print(value)\n"
  },
  {
    "path": "Intermediate/00_dates.py",
    "content": "# Clase en vídeo: https://youtu.be/TbcEqkabAWU\n\n### Dates ###\n\n# Date time\n\nfrom datetime import timedelta\nfrom datetime import date\nfrom datetime import time\nfrom datetime import datetime\n\nnow = datetime.now()\n\n\ndef print_date(date):\n    print(date.year)\n    print(date.month)\n    print(date.day)\n    print(date.hour)\n    print(date.minute)\n    print(date.second)\n    print(date.timestamp())\n\n\nprint_date(now)\n\nyear_2023 = datetime(2023, 1, 1)\n\nprint_date(year_2023)\n\n# Time\n\n\ncurrent_time = time(21, 6, 0)\n\nprint(current_time.hour)\nprint(current_time.minute)\nprint(current_time.second)\n\n# Date\n\n\ncurrent_date = date.today()\n\nprint(current_date.year)\nprint(current_date.month)\nprint(current_date.day)\n\ncurrent_date = date(2022, 10, 6)\n\nprint(current_date.year)\nprint(current_date.month)\nprint(current_date.day)\n\ncurrent_date = date(current_date.year,\n                    current_date.month + 1, current_date.day)\n\nprint(current_date.month)\n\n# Operaciones con fechas\n\ndiff = year_2023 - now\nprint(diff)\n\ndiff = year_2023.date() - current_date\nprint(diff)\n\n# Timedelta\n\n\nstart_timedelta = timedelta(200, 100, 100, weeks=10)\nend_timedelta = timedelta(300, 100, 100, weeks=13)\n\nprint(end_timedelta - start_timedelta)\nprint(end_timedelta + start_timedelta)\n"
  },
  {
    "path": "Intermediate/01_list_comprehension.py",
    "content": "# Clase en vídeo: https://youtu.be/TbcEqkabAWU?t=3239\n\n### List Comprehension ###\n\nmy_original_list = [0, 1, 2, 3, 4, 5, 6, 7]\nprint(my_original_list)\n\nmy_range = range(8)\nprint(list(my_range))\n\n# Definición\n\nmy_list = [i + 1 for i in range(8)]\nprint(my_list)\n\nmy_list = [i * 2 for i in range(8)]\nprint(my_list)\n\nmy_list = [i * i for i in range(8)]\nprint(my_list)\n\n\ndef sum_five(number):\n    return number + 5\n\n\nmy_list = [sum_five(i) for i in range(8)]\nprint(my_list)\n"
  },
  {
    "path": "Intermediate/02_challenges.py",
    "content": "# Clase en vídeo: https://youtu.be/TbcEqkabAWU?t=4142\n\n### Challenges ###\n\n\"\"\"\nEL FAMOSO \"FIZZ BUZZ”:\nEscribe un programa que muestre por consola (con un print) los\nnúmeros de 1 a 100 (ambos incluidos y con un salto de línea entre\ncada impresión), sustituyendo los siguientes:\n- Múltiplos de 3 por la palabra \"fizz\".\n- Múltiplos de 5 por la palabra \"buzz\".\n- Múltiplos de 3 y de 5 a la vez por la palabra \"fizzbuzz\".\n\"\"\"\n\n\ndef fizzbuzz():\n    for index in range(1, 101):\n        if index % 3 == 0 and index % 5 == 0:\n            print(\"fizzbuzz\")\n        elif index % 3 == 0:\n            print(\"fizz\")\n        elif index % 5 == 0:\n            print(\"buzz\")\n        else:\n            print(index)\n\n\nfizzbuzz()\n\n\"\"\"\n¿ES UN ANAGRAMA?\nEscribe una función que reciba dos palabras (String) y retorne\nverdadero o falso (Bool) según sean o no anagramas.\n- Un Anagrama consiste en formar una palabra reordenando TODAS\n  las letras de otra palabra inicial.\n- NO hace falta comprobar que ambas palabras existan.\n- Dos palabras exactamente iguales no son anagrama.\n\"\"\"\n\n\ndef is_anagram(word_one, word_two):\n    if word_one.lower() == word_two.lower():\n        return False\n    return sorted(word_one.lower()) == sorted(word_two.lower())\n\n\nprint(is_anagram(\"Amor\", \"Roma\"))\n\n\"\"\"\nLA SUCESIÓN DE FIBONACCI\nEscribe un programa que imprima los 50 primeros números de la sucesión\nde Fibonacci empezando en 0.\n- La serie Fibonacci se compone por una sucesión de números en\n  la que el siguiente siempre es la suma de los dos anteriores.\n  0, 1, 1, 2, 3, 5, 8, 13...\n\"\"\"\n\n\ndef fibonacci():\n\n    prev = 0\n    next = 1\n\n    for index in range(0, 50):\n        print(prev)\n        fib = prev + next\n        prev = next\n        next = fib\n\n\nfibonacci()\n\n\"\"\"\n¿ES UN NÚMERO PRIMO?\nEscribe un programa que se encargue de comprobar si un número es o no primo.\nHecho esto, imprime los números primos entre 1 y 100.\n\"\"\"\n\n\ndef is_prime():\n\n    for number in range(1, 101):\n\n        if number >= 2:\n\n            is_divisible = False\n\n            for index in range(2, number):\n                if number % index == 0:\n                    is_divisible = True\n                    break\n\n            if not is_divisible:\n                print(number)\n\n\nis_prime()\n\n\"\"\"\nINVIRTIENDO CADENAS\nCrea un programa que invierta el orden de una cadena de texto\nsin usar funciones propias del lenguaje que lo hagan de forma automática.\n- Si le pasamos \"Hola mundo\" nos retornaría \"odnum aloH\"\n\"\"\"\n\n\ndef reverse(text):\n    text_len = len(text)\n    reversed_text = \"\"\n    for index in range(0, text_len):\n        reversed_text += text[text_len - index - 1]\n    return reversed_text\n\n\nprint(reverse(\"Hola mundo\"))\n"
  },
  {
    "path": "Intermediate/03_lambdas.py",
    "content": "# Clase en vídeo: https://youtu.be/TbcEqkabAWU?t=9145\n\n### Lambdas ###\n\nsum_two_values = lambda first_value, second_value: first_value + second_value\nprint(sum_two_values(2, 4))\n\nmultiply_values = lambda first_value, second_value: first_value * second_value - 3\nprint(multiply_values(2, 4))\n\ndef sum_three_values(value):\n    return lambda first_value, second_value: first_value + second_value + value\n\nprint(sum_three_values(5)(2, 4))"
  },
  {
    "path": "Intermediate/04_higher_order_functions.py",
    "content": "# Clase en vídeo: https://youtu.be/TbcEqkabAWU?t=10172\n\n### Higher Order Functions ###\n\nfrom functools import reduce\n\n\ndef sum_one(value):\n    return value + 1\n\n\ndef sum_five(value):\n    return value + 5\n\n\ndef sum_two_values_and_add_value(first_value, second_value, f_sum):\n    return f_sum(first_value + second_value)\n\n\nprint(sum_two_values_and_add_value(5, 2, sum_one))\nprint(sum_two_values_and_add_value(5, 2, sum_five))\n\n### Closures ###\n\n\ndef sum_ten(original_value):\n    def add(value):\n        return value + 10 + original_value\n    return add\n\n\nadd_closure = sum_ten(1)\nprint(add_closure(5))\nprint((sum_ten(5))(1))\n\n### Built-in Higher Order Functions ###\n\nnumbers = [2, 5, 10, 21, 3, 30]\n\n# Map\n\n\ndef multiply_two(number):\n    return number * 2\n\n\nprint(list(map(multiply_two, numbers)))\nprint(list(map(lambda number: number * 2, numbers)))\n\n# Filter\n\n\ndef filter_greater_than_ten(number):\n    if number > 10:\n        return True\n    return False\n\n\nprint(list(filter(filter_greater_than_ten, numbers)))\nprint(list(filter(lambda number: number > 10, numbers)))\n\n# Reduce\n\n\ndef sum_two_values(first_value, second_value):\n    return first_value + second_value\n\n\nprint(reduce(sum_two_values, numbers))\n"
  },
  {
    "path": "Intermediate/05_error_types.py",
    "content": "# Clase en vídeo: https://youtu.be/TbcEqkabAWU?t=12721\n\n### Error Types ###\n\n# SyntaxError\n# print \"¡Hola comunidad!\" # Descomentar para Error\nfrom math import pi\nimport math\nprint(\"¡Hola comunidad!\")\n\n# NameError\nlanguage = \"Spanish\"  # Comentar para Error\nprint(language)\n\n# IndexError\nmy_list = [\"Python\", \"Swift\", \"Kotlin\", \"Dart\", \"JavaScript\"]\nprint(my_list[0])\nprint(my_list[4])\nprint(my_list[-1])\n# print(my_list[5]) # Descomentar para Error\n\n# ModuleNotFoundError\n# import maths # Descomentar para Error\n\n# AttributeError\n# print(math.PI) # Descomentar para Error\nprint(math.pi)\n\n# KeyError\nmy_dict = {\"Nombre\": \"Brais\", \"Apellido\": \"Moure\", \"Edad\": 35, 1: \"Python\"}\nprint(my_dict[\"Edad\"])\n# print(my_dict[\"Apelido\"]) # Descomentar para Error\nprint(my_dict[\"Apellido\"])\n\n# TypeError\n# print(my_list[\"0\"]) # Descomentar para Error\nprint(my_list[0])\nprint(my_list[False])\n\n# ImportError\n# from math import PI # Descomentar para Error\nprint(pi)\n\n# ValueError\n# my_int = int(\"10 Años\") # Descomentar para Error\nmy_int = int(\"10\")\nprint(type(my_int))\n\n# ZeroDivisionError\n# print(4/0) # Descomentar para Error\nprint(4/2)\n"
  },
  {
    "path": "Intermediate/06_file_handling.py",
    "content": "# Clase en vídeo: https://youtu.be/TbcEqkabAWU?t=15524\n\n### File Handling ###\n\nimport xml\nimport csv\nimport json\nimport os\n\n# .txt file\n\n# Leer, escribir y sobrescribir si ya existe\ntxt_file = open(\"my_file.txt\", \"w+\")\n\ntxt_file.write(\n    \"Mi nombre es Brais\\nMi apellido es Moure\\n35 años\\nY mi lenguaje preferido es Python\")\n\n# Posiciona el cursor al inicio del fichero\ntxt_file.seek(0)\n\n# Lee e imprime todo el contenido del fichero\nprint(txt_file.read())\n\n# Lee e imprime 10 caracteres desde el inicio del fichero\ntxt_file.seek(0)\nprint(txt_file.read(10))\n\n# Lee e imprime el resto de la línea actual desde la posición 11\nprint(txt_file.readline())\n\n# Lee e imprime la siguiente línea\nprint(txt_file.readline())\n\n# Lee e imprime las líneas restantes del fichero\nfor line in txt_file.readlines():\n    print(line)\n\n# Escribe una nueva línea en el fichero\ntxt_file.write(\"\\nAunque también me gusta Kotlin\")\n\n# Posiciona el cursor al inicio del fichero, lee e imprime todo su contenido\ntxt_file.seek(0)\nprint(txt_file.read())\n\n# Cierra el fichero\ntxt_file.close()\n\n# Agrega una nueva línea en el fichero\nwith open(\"my_file.txt\", \"a\") as my_other_file:\n    my_other_file.write(\"\\nY Swift\")\n\n# os.remove(\"Intermediate/my_file.txt\")\n\n# .json file\n\njson_file = open(\"Intermediate/my_file.json\", \"w+\")\n\njson_test = {\n    \"name\": \"Brais\",\n    \"surname\": \"Moure\",\n    \"age\": 35,\n    \"languages\": [\"Python\", \"Swift\", \"Kotlin\"],\n    \"website\": \"https://moure.dev\"}\n\njson.dump(json_test, json_file, indent=2)\n\njson_file.close()\n\nwith open(\"Intermediate/my_file.json\") as my_other_file:\n    for line in my_other_file.readlines():\n        print(line)\n\njson_dict = json.load(open(\"Intermediate/my_file.json\"))\nprint(json_dict)\nprint(type(json_dict))\nprint(json_dict[\"name\"])\n\n# .csv file\n\n\ncsv_file = open(\"Intermediate/my_file.csv\", \"w+\")\n\ncsv_writer = csv.writer(csv_file)\ncsv_writer.writerow([\"name\", \"surname\", \"age\", \"language\", \"website\"])\ncsv_writer.writerow([\"Brais\", \"Moure\", 35, \"Python\", \"https://moure.dev\"])\ncsv_writer.writerow([\"Roswell\", \"\", 2, \"COBOL\", \"\"])\n\ncsv_file.close()\n\nwith open(\"Intermediate/my_file.csv\") as my_other_file:\n    for line in my_other_file.readlines():\n        print(line)\n\n# .xlsx file\n# import xlrd # Debe instalarse el módulo\n\n# .xml file\n\n# ¿Te atreves a practicar cómo trabajar con este tipo de ficheros?\n"
  },
  {
    "path": "Intermediate/07_regular_expressions.py",
    "content": "# Clase en vídeo: https://youtu.be/TbcEqkabAWU?t=19762\n\n### Regular Expressions ###\n\nimport re\n\n# match\n\nmy_string = \"Esta es la lección número 7: Lección llamada Expresiones Regulares\"\nmy_other_string = \"Esta no es la lección número 6: Manejo de ficheros\"\n\nmatch = re.match(\"Esta es la lección\", my_string, re.I)\nprint(match)\nstart, end = match.span()\nprint(my_string[start:end])\n\nmatch = re.match(\"Esta no es la lección\", my_other_string)\n# if not(match == None): # Otra forma de comprobar el None\n# if match != None: # Otra forma de comprobar el None\nif match is not None:\n    print(match)\n    start, end = match.span()\n    print(my_other_string[start:end])\n\nprint(re.match(\"Expresiones Regulares\", my_string))\n\n# search\n\nsearch = re.search(\"lección\", my_string, re.I)\nprint(search)\nstart, end = search.span()\nprint(my_string[start:end])\n\n# findall\n\nfindall = re.findall(\"lección\", my_string, re.I)\nprint(findall)\n\n# split\n\nprint(re.split(\":\", my_string))\n\n# sub\n\nprint(re.sub(\"[l|L]ección\", \"LECCIÓN\", my_string))\nprint(re.sub(\"Expresiones Regulares\", \"RegEx\", my_string))\n\n### Regular Expressions Patterns ###\n\n# Para aprender y validar expresiones regulares: https://regex101.com\n\npattern = r\"[lL]ección\"\nprint(re.findall(pattern, my_string))\n\npattern = r\"[lL]ección|Expresiones\"\nprint(re.findall(pattern, my_string))\n\npattern = r\"[0-9]\"\nprint(re.findall(pattern, my_string))\nprint(re.search(pattern, my_string))\n\npattern = r\"\\d\"\nprint(re.findall(pattern, my_string))\n\npattern = r\"\\D\"\nprint(re.findall(pattern, my_string))\n\npattern = r\"[l].*\"\nprint(re.findall(pattern, my_string))\n\nemail = \"mouredev@mouredev.com\"\npattern = r\"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z-.]+$\"\nprint(re.match(pattern, email))\nprint(re.search(pattern, email))\nprint(re.findall(pattern, email))\n\nemail = \"mouredev@mouredev.com.mx\"\nprint(re.findall(pattern, email))\n"
  },
  {
    "path": "Intermediate/08_python_package_manager.py",
    "content": "# Clase en vídeo: https://youtu.be/TbcEqkabAWU?t=24010\n\n### Python Package Manager ###\n\n# PIP https://pypi.org\n\n# pip install pip\n# pip --version\n\n# pip install numpy\nimport pandas\nfrom mypackage import arithmetics\nimport requests\nimport numpy\n\nprint(numpy.version.version)\n\nnumpy_array = numpy.array([35, 24, 62, 52, 30, 30, 17])\nprint(type(numpy_array))\n\nprint(numpy_array * 2)\n\n# pip install pandas\n\n# pip list\n# pip uninstall pandas\n# pip show numpy\n\n# pip install requests\n\nresponse = requests.get(\"https://pokeapi.co/api/v2/pokemon?limit=151\")\nprint(response)\nprint(response.status_code)\nprint(response.json())\n\n# Arithmetics Package\n\n\nprint(arithmetics.sum_two_values(1, 4))\n"
  },
  {
    "path": "Intermediate/my_file.csv",
    "content": "name,surname,age,language,website\r\nBrais,Moure,35,Python,https://moure.dev\r\nRoswell,,2,COBOL,\r\n"
  },
  {
    "path": "Intermediate/my_file.json",
    "content": "{\n  \"name\": \"Brais\",\n  \"surname\": \"Moure\",\n  \"age\": 35,\n  \"languages\": [\n    \"Python\",\n    \"Swift\",\n    \"Kotlin\"\n  ],\n  \"website\": \"https://moure.dev\"\n}"
  },
  {
    "path": "Intermediate/my_file.txt",
    "content": "Mi nombre es Brais\nMi apellido es Moure\n35 años\nY mi lenguaje preferido es Python\nAunque también me gusta Kotlin\nY Swift"
  },
  {
    "path": "Intermediate/mypackage/__init__.py",
    "content": ""
  },
  {
    "path": "Intermediate/mypackage/arithmetics.py",
    "content": "# Clase en vídeo: https://youtu.be/TbcEqkabAWU?t=24010\n\n### Arithmetics ###\n\ndef sum_two_values(first_value, second_value):\n    return first_value + second_value\n"
  },
  {
    "path": "LICENSE",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "README.md",
    "content": "# Hello Python\n\n[![Python](https://img.shields.io/badge/Python-3.10+-yellow?style=for-the-badge&logo=python&logoColor=white&labelColor=101010)](https://python.org)\n[![FastAPI](https://img.shields.io/badge/FastAPI-0.88.0+-00a393?style=for-the-badge&logo=fastapi&logoColor=white&labelColor=101010)](https://fastapi.tiangolo.com)\n[![MongoDB](https://img.shields.io/badge/MongoDB-6.0+-00684A?style=for-the-badge&logo=mongodb&logoColor=white&labelColor=101010)](https://www.mongodb.com)\n[![ChatGPT](https://img.shields.io/badge/ChatGPT-GPT--4-7CF178?style=for-the-badge&logo=openai&logoColor=white&labelColor=101010)](https://platform.openai.com)\n[![Reflex](https://img.shields.io/badge/Reflex-0.4.6+-5646ED?style=for-the-badge&logo=reflex&logoColor=white&labelColor=101010)](https://reflex.dev)\n\n## Curso para aprender el lenguaje de programación Python desde cero y para principiantes\n\n![](./Images/header.jpg)\n\n### Proyecto realizado durante emisiones en directo desde [Twitch](https://twitch.tv/mouredev)\n\n> ##### Si consideras útil el curso, apóyalo haciendo \"★ Star\" en el repositorio. ¡Gracias!\n\n## Clases en vídeo\n\n### Curso de fundamentos desde cero\n\nCurso que agrupa todas las clases en directo que hacen referencia a los fundamentos de Python.\n\n> Código: Directorio \"Basic\" en el proyecto\n\n<a href=\"https://youtu.be/Kp4Mvapo5kc\"><img src=\"http://i3.ytimg.com/vi/Kp4Mvapo5kc/maxresdefault.jpg\" style=\"height: 50%; width:50%;\"/></a>\n\n- [Introducción](https://youtu.be/Kp4Mvapo5kc)\n- [Contexto](https://youtu.be/Kp4Mvapo5kc?t=244)\n- [Lección 1 - Configuración](https://youtu.be/Kp4Mvapo5kc?t=850)\n- [Lección 2 - Hola Mundo](https://youtu.be/Kp4Mvapo5kc?t=1518)\n- [Lección 3 - Variables](https://youtu.be/Kp4Mvapo5kc?t=2938)\n- [Lección 4 - Operadores](https://youtu.be/Kp4Mvapo5kc?t=5665)\n- [Lección 5 - Strings](https://youtu.be/Kp4Mvapo5kc?t=8643)\n- [Lección 6 - Listas](https://youtu.be/Kp4Mvapo5kc?t=10872)\n- [Lección 7 - Tuplas](https://youtu.be/Kp4Mvapo5kc?t=14711)\n- [Lección 8 - Sets](https://youtu.be/Kp4Mvapo5kc?t=16335)\n- [Lección 9 - Diccionarios](https://youtu.be/Kp4Mvapo5kc?t=18506)\n- [Lección 10 - Condicionales](https://youtu.be/Kp4Mvapo5kc?t=21442)\n- [Lección 11 - Bucles/Loops/Ciclos](https://youtu.be/Kp4Mvapo5kc?t=23822)\n- [Lección 12 - Funciones](https://youtu.be/Kp4Mvapo5kc?t=26619)\n- [Lección 13 - Clases](https://youtu.be/Kp4Mvapo5kc?t=29327)\n- [Lección 14 - Excepciones](https://youtu.be/Kp4Mvapo5kc?t=32030)\n- [Lección 15 - Módulos](https://youtu.be/Kp4Mvapo5kc?t=34583)\n- [Próximos pasos](https://youtu.be/Kp4Mvapo5kc?t=36390)\n\n### Curso intermedio de fundamentos desde cero\n\nCurso en el que continuamos aprendiendo Python desde sus bases, siguiendo la ruta de aprendizaje desde la última lección del curso de inicial.\n\n> Código: Directorio \"Intermediate\" en el proyecto\n\n<a href=\"https://youtu.be/TbcEqkabAWU\"><img src=\"http://i3.ytimg.com/vi/TbcEqkabAWU/maxresdefault.jpg\" style=\"height: 50%; width:50%;\"/></a>\n\n- [Introducción](https://youtu.be/TbcEqkabAWU)\n- [Lección 1 - Dates](https://youtu.be/TbcEqkabAWU?t=202)\n- [Lección 2 - List Comprehension](https://youtu.be/TbcEqkabAWU?t=3239)\n- [Lección 3 - Resolución de retos de programación](https://youtu.be/TbcEqkabAWU?t=4142)\n- [Lección 4 - Lambdas](https://youtu.be/TbcEqkabAWU?t=9145)\n- [Lección 5 - Funciones de orden superior](https://youtu.be/TbcEqkabAWU?t=10172)\n- [Lección 6 - Tipos de error](https://youtu.be/TbcEqkabAWU?t=12721)\n- [Lección 7 - Manejo de ficheros](https://youtu.be/TbcEqkabAWU?t=15524)\n- [Lección 8 - Expresiones regulares](https://youtu.be/TbcEqkabAWU?t=19762)\n- [Lección 9 - Manejo de paquetes](https://youtu.be/TbcEqkabAWU?t=24010)\n- [Próximos pasos](https://youtu.be/TbcEqkabAWU?t=26228)\n\n### Backend desde cero\n\nCurso en el que aprenderemos a utilizar Python para backend e implementaremos un API REST con autenticación, base de datos y desplegaremos el proyecto en un servidor real.\n\n> Código: Directorio \"Backend\" en el proyecto\n\n<a href=\"https://youtu.be/_y9qQZXE24A\"><img src=\"http://i3.ytimg.com/vi/_y9qQZXE24A/maxresdefault.jpg\" style=\"height: 50%; width:50%;\"/></a>\n\n- [Introducción](https://youtu.be/_y9qQZXE24A)\n- [Lección 01 - ¿Qué es un backend?](https://youtu.be/_y9qQZXE24A?t=125)\n- [Lección 02 - API y FastAPI](https://youtu.be/_y9qQZXE24A?t=834)\n- [Lección 03 - Type Hints](https://youtu.be/_y9qQZXE24A?t=1810)\n- [Lección 04 - Configuración FastAPI](https://youtu.be/_y9qQZXE24A?t=2629)\n- [Lección 05 - Hola mundo](https://youtu.be/_y9qQZXE24A?t=3504)\n- [Lección 06 - Operación GET](https://youtu.be/_y9qQZXE24A?t=5382)\n- [Lección 07 - Peticiones HTTP](https://youtu.be/_y9qQZXE24A?t=5925)\n- [Lección 08 - Creación API](https://youtu.be/_y9qQZXE24A?t=6099)\n- [Lección 09 - Path y Query](https://youtu.be/_y9qQZXE24A?t=7510)\n- [Lección 10 - Operaciones POST, PUT y DELETE](https://youtu.be/_y9qQZXE24A?t=8529)\n- [Lección 11 - HTTP status codes](https://youtu.be/_y9qQZXE24A?t=11072)\n- [Lección 12 - Routers](https://youtu.be/_y9qQZXE24A?t=12475)\n- [Lección 13 - Recursos estáticos](https://youtu.be/_y9qQZXE24A?t=13618)\n- [Lección 14 - Autorización OAuth2](https://youtu.be/_y9qQZXE24A?t=14094)\n- [Lección 15 - OAuth2 JWT](https://youtu.be/_y9qQZXE24A?t=17664)\n- [Lección 16 - MongoDB](https://youtu.be/_y9qQZXE24A?t=20480)\n- [Lección 17 - MongoDB Atlas](https://youtu.be/_y9qQZXE24A?t=25470)\n- [Lección 18 - Despliegue en Deta \\*](https://youtu.be/_y9qQZXE24A?t=27335)\n- [Próximos pasos](https://youtu.be/_y9qQZXE24A?t=28484)\n\n**\\*ACTUALIZACIÓN Sobre la lección 18:** Deta, el servicio utilizado para el despliegue durante el curso, ya no existe. Te recomiendo revisar la documentación oficial de FastAPI sobre [despliegue](https://fastapi.tiangolo.com/deployment/). Puedes utilizar alguno de los [proveedores gratuitos](https://fastapi.tiangolo.com/deployment/cloud/) recomendados. En mi caso, te dejo el [fichero de configuración](./Backend/FastAPI/vercel.json) y el de [dependencias](./Backend/FastAPI/requirements.txt) para hacerlo desde [Vercel](https://vercel.com/) (al crear el proyecto en la plataforma selecciona que el directorio \"Backend/FastAPI\" es el root).\n\n### Frontend desde cero\n\nCursos en los que aprenderemos a utilizar Python para desarrollo web con dos proyectos reales desplegados en producción. Tutoriales en vídeo paso a paso con 9 horas de contenido.\n\n<a href=\"https://github.com/mouredev/python-web\"><img src=\"http://i3.ytimg.com/vi/n2YrGsXJC6Y/maxresdefault.jpg\" style=\"height: 50%; width:50%;\"/></a>\n\n<a href=\"https://github.com/mouredev/python-web\"><img src=\"http://i3.ytimg.com/vi/bNy8OZJfA6I/maxresdefault.jpg\" style=\"height: 50%; width:50%;\"/></a>\n\n[![Curso Python Web](https://img.shields.io/github/stars/mouredev/python-web?label=Curso%20Python%20web&style=social)](https://github.com/mouredev/python-web)\n\n<a href=\"https://github.com/mouredev/adeviento-web\"><img src=\"http://i3.ytimg.com/vi/h8Tn0ITRoQs/maxresdefault.jpg\" style=\"height: 50%; width:50%;\"/></a>\n\n[![Curso Python Web](https://img.shields.io/github/stars/mouredev/adeviento-web?label=Tutorial%20Python%20web%20extra&style=social)](https://github.com/mouredev/adeviento-web)\n\n### Aprende a integrar ChatGPT en tu proyecto desde cero\n\n<a href=\"https://youtu.be/b8COygWdvmw\"><img src=\"http://i3.ytimg.com/vi/b8COygWdvmw/maxresdefault.jpg\" style=\"height: 50%; width:50%;\"/></a>\n\nClase de una hora de duración donde aprenderás a interactuar con ChatGPT desde tu aplicación, mantener conversaciones y establecer el contexto de la IA para potenciar tu proyecto.\n\nCon todo el código publicado [aquí](https://gist.github.com/mouredev/58abfbcef017efaf3852e8821564c011).\n\n### Traductor de Voz con IA\n\n<a href=\"https://youtu.be/oxLvf2nDCvQ\"><img src=\"http://i3.ytimg.com/vi/oxLvf2nDCvQ/maxresdefault.jpg\" style=\"height: 50%; width:50%;\"/></a>\n\nAprende a desarrollar un traductor de voz a varios idiomas utilizando con IA. Creando su Web y todo en 100 líneas.\n\nCon todo el código publicado [aquí](https://gist.github.com/mouredev/0ea42112751f0187d90d5403d1f333e2).\n\n### Introducción al Testing\n\nTaller de introducción a testing con Python creado junto a [Carlos Blé](https://www.carlosble.com) y [Miguel A. Gómez](https://softwarecrafters.io), expertos en la materia.\n\n<a href=\"https://youtu.be/344uwF1z2Gg\"><img src=\"http://i3.ytimg.com/vi/344uwF1z2Gg/maxresdefault.jpg\" style=\"height: 50%; width:50%;\"/></a>\n\n### Extra: 15 curiosidades sobre Python\n\nY para finalizar... ¿Quieres saber aun más sobre él? Aquí tienes 15 curiosidades que quizás no conozcas sobre el lenguaje.\n\n<a href=\"https://youtu.be/q2lCm2KAz3w\"><img src=\"http://i3.ytimg.com/vi/q2lCm2KAz3w/maxresdefault.jpg\" style=\"height: 50%; width:50%;\"/></a>\n\n## Información importante y preguntas frecuentes\n\nActualmente el curso está en pausa. Se han finalizados los bloques básico, intermedio y backend, y ese era el objetivo inicial del proyecto.\nNo descarto añadir nuevas lecciones a futuro, pero creo que por el momento puede servir de base a cualquier persona que quiera empezar a aprender este lenguaje.\n\n- Recuerda que he creado en el [Discord](https://discord.gg/mouredev) un canal \"🐍python\" para que puedas comentar lo que quieras.\n- En el momento que el curso continúe, actualizaré el repositorio y avisaré en redes.\n\n¡Muchísimas gracias por todo el apoyo mostrado!\n\n## Enlaces de interés\n\n- [Web oficial de Python](https://www.python.org/)\n- [Tutorial oficial de Python en Español](https://docs.python.org/es/3/tutorial/index.html)\n- [Repo 30 días de Python](https://github.com/Asabeneh/30-Days-Of-Python)\n- [Juego Codédex para aprender Python](https://www.codedex.io/)\n- [Visual Studio Code](https://code.visualstudio.com/): El editor que estoy usando\n- [FastAPI](https://fastapi.tiangolo.com/es/): El framework para crear nuestra API Backend\n- [MongoDB](https://www.mongodb.com/): La base de datos que utiliza nuestro backend\n- [Vercel](https://vercel.com/): Para desplegar nuestra API en la nube\n\n## Únete al campus de programación de la comunidad\n\n![https://mouredev.pro](./Images/pro.jpg)\n\n#### Te presento [mouredev pro](https://mouredev.pro), mi proyecto más importante para ayudarte a estudiar programación y desarrollo de software de manera diferente.\n\n> **¿Buscas un extra?** Aquí encontrarás este y otros cursos editados por lecciones individuales, para avanzar a tu ritmo y guardar el progreso. También dispondrás de ejercicios y correcciones, test para validar tus conocimientos, examen y certificado público de finalización, soporte, foro de estudiantes, reunionnes grupales, cursos exclusivos y mucho más.\n>\n> Entra en **[mouredev.pro](https://mouredev.pro)** y utiliza el cupón **\"PYTHON\"** con un 10% de descuento en tu primera suscripción.\n\n## ![https://mouredev.com](https://raw.githubusercontent.com/mouredev/mouredev/master/mouredev_emote.png) Hola, mi nombre es Brais Moure.\n\n### Freelance full-stack iOS & Android engineer\n\n[![YouTube Channel Subscribers](https://img.shields.io/youtube/channel/subscribers/UCxPD7bsocoAMq8Dj18kmGyQ?style=social)](https://youtube.com/mouredevapps?sub_confirmation=1)\n[![Twitch Status](https://img.shields.io/twitch/status/mouredev?style=social)](https://twitch.com/mouredev)\n[![Discord](https://img.shields.io/discord/729672926432985098?style=social&label=Discord&logo=discord)](https://mouredev.com/discord)\n[![Twitter Follow](https://img.shields.io/twitter/follow/mouredev?style=social)](https://twitter.com/mouredev)\n![GitHub Followers](https://img.shields.io/github/followers/mouredev?style=social)\n![GitHub Followers](https://img.shields.io/github/stars/mouredev?style=social)\n\nSoy ingeniero de software desde 2010. Desde 2018 combino mi trabajo desarrollando Apps con la creación de contenido formativo sobre programación y tecnología en diferentes redes sociales como **[@mouredev](https://moure.dev)**.\n\nSi quieres unirte a nuestra comunidad de desarrollo, aprender programación, mejorar tus habilidades y ayudar a la continuidad del proyecto, puedes encontrarnos en:\n\n[![Twitch](https://img.shields.io/badge/Twitch-Programación_en_directo-9146FF?style=for-the-badge&logo=twitch&logoColor=white&labelColor=101010)](https://twitch.tv/mouredev)\n[![Discord](https://img.shields.io/badge/Discord-Servidor_de_la_comunidad-5865F2?style=for-the-badge&logo=discord&logoColor=white&labelColor=101010)](https://mouredev.com/discord) [![Pro](https://img.shields.io/badge/Cursos-mouredev.pro-FF5500?style=for-the-badge&logo=gnometerminal&logoColor=white&labelColor=101010)](https://moure.dev)\n[![Link](https://img.shields.io/badge/Links_de_interés-moure.dev-14a1f0?style=for-the-badge&logo=Linktree&logoColor=white&labelColor=101010)](https://moure.dev) [![Web](https://img.shields.io/badge/GitHub-MoureDev-087ec4?style=for-the-badge&logo=github&logoColor=white&labelColor=101010)](https://github.com/mouredev)\n"
  }
]