Video Tutorial
Documentación
En este tutorial aprenderás a instalar y ejecutar las dependencias necesarias para utilizar el script de Generación de Códigos QR en Python. Es un proceso sencillo compatible con Windows, macOS y Linux que te permitirá ejecutar la herramienta en tu computadora sin complicaciones.
¿Qué necesitas instalar?
- Python 3.10 o superior — El lenguaje en el que está hecho el script.
- customtkinter — Librería para la interfaz del programa.
- qrcode[pil] y Pillow — Librerías encargadas de generar y procesar las imágenes de los códigos QR.
Instalación en Windows
Opción A: Instalador automático (más fácil)
Descarga el proyecto y haz doble clic en el archivo instalador.bat. Él instalará
automáticamente todas las librerías necesarias.
Opción B: Instalación manual paso a paso
Paso 1 — Instalar Python
- Entra a python.org/downloads y descarga la versión más reciente.
- Abre el instalador y marca la casilla "Add Python to PATH" antes de continuar.
- Haz clic en Install Now y espera a que termine.
Paso 2 — Instalar las librerías
Abre el Símbolo del sistema (busca "cmd" en el menú inicio) y pega esto:
py -m pip install customtkinter qrcode[pil] Pillow
Paso 3 — Ejecutar el script
Haz doble clic en el archivo qr_generator.py o ejecútalo con:
py qr_generator.py
Instalación en macOS
Paso 1 — Instalar Python
Descarga desde python.org o usa Homebrew:
brew install python
Paso 2 — Instalar las librerías
pip3 install customtkinter "qrcode[pil]" Pillow
Paso 3 — Ejecutar el script
python3 qr_generator.py
Instalación en Linux (Ubuntu / Debian)
Paso 1 — Instalar Python y Tkinter
Verifica e instala Python con soporte gráfico:
sudo apt update && sudo apt install python3 python3-pip python3-tk -y
Paso 2 — Instalar las librerías
pip3 install customtkinter "qrcode[pil]" Pillow
Paso 3 — Ejecutar el script
python3 qr_generator.py
Librerías Necesarias
py -m pip install customtkinter qrcode[pil] Pillow
Código Fuente Principal
"""
Generador de Códigos QR — Interfaz Gráfica Moderna
Requiere: qrcode[pil], Pillow, customtkinter
"""
import customtkinter as ctk
from tkinter import filedialog, messagebox, colorchooser
import qrcode
from qrcode.image.styledpil import StyledPilImage
from qrcode.image.styles.moduledrawers.pil import (
RoundedModuleDrawer, CircleModuleDrawer, SquareModuleDrawer,
GappedSquareModuleDrawer, VerticalBarsDrawer, HorizontalBarsDrawer
)
from qrcode.image.styles.colormasks import SolidFillColorMask
from PIL import Image
import os
import threading
# ── Tema ────────────────────────────────────────────────────────────────────
ctk.set_appearance_mode("dark")
ctk.set_default_color_theme("blue")
ACCENT = "#6C63FF"
ACCENT2 = "#FF6584"
BG_DARK = "#0F0F1A"
BG_CARD = "#1A1A2E"
BG_WIDGET = "#16213E"
TEXT_LIGHT = "#E0E0FF"
TEXT_MUTED = "#8888AA"
DRAWER_MAP = {
"Cuadrado" : SquareModuleDrawer,
"Redondeado" : RoundedModuleDrawer,
"Circulos" : CircleModuleDrawer,
"Barras Vert." : VerticalBarsDrawer,
"Barras Horiz." : HorizontalBarsDrawer,
"Separado" : GappedSquareModuleDrawer,
}
ERROR_MAP = {
"L - 7%" : qrcode.constants.ERROR_CORRECT_L,
"M - 15%" : qrcode.constants.ERROR_CORRECT_M,
"Q - 25%" : qrcode.constants.ERROR_CORRECT_Q,
"H - 30%" : qrcode.constants.ERROR_CORRECT_H,
}
def hex_to_rgb(hex_color: str) -> tuple:
hex_color = hex_color.lstrip("#")
return tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4))
class ColorButton(ctk.CTkFrame):
"""Boton compacto que muestra el color elegido y abre el selector."""
def __init__(self, master, label: str, initial: str = "#000000", **kw):
super().__init__(master, fg_color=BG_WIDGET, corner_radius=10, **kw)
self.color = initial
self._cb = None
ctk.CTkLabel(self, text=label, text_color=TEXT_MUTED,
font=("Segoe UI", 11)).pack(side="left", padx=(10, 6))
self.swatch = ctk.CTkLabel(self, text=" ", width=28, height=22,
corner_radius=6, fg_color=initial)
self.swatch.pack(side="left")
self.hex_lbl = ctk.CTkLabel(self, text=initial, text_color=TEXT_LIGHT,
font=("Segoe UI Semibold", 11), width=70)
self.hex_lbl.pack(side="left", padx=4)
btn = ctk.CTkButton(self, text="✏", width=28, height=28,
fg_color=ACCENT, hover_color=ACCENT2,
font=("Segoe UI", 13), corner_radius=7,
command=self._pick)
btn.pack(side="right", padx=8)
def _pick(self):
result = colorchooser.askcolor(color=self.color, title="Elegir color")
if result and result[1]:
self.set_color(result[1])
if self._cb:
self._cb(result[1])
def set_color(self, hex_color: str):
self.color = hex_color
self.swatch.configure(fg_color=hex_color)
self.hex_lbl.configure(text=hex_color)
def on_change(self, callback):
self._cb = callback
class QRApp(ctk.CTk):
def __init__(self):
super().__init__()
self.title("QR Studio — Generador de Codigos QR")
self.geometry("980x720")
self.minsize(900, 660)
self.configure(fg_color=BG_DARK)
self._qr_image = None
self._preview_tk = None
self._gen_thread = None
self._build_ui()
self.after(200, self._schedule_preview) # genera un QR inicial
# ── Construccion de la UI ────────────────────────────────────────────────
def _build_ui(self):
self.grid_columnconfigure(0, weight=2, minsize=380)
self.grid_columnconfigure(1, weight=3)
self.grid_rowconfigure(0, weight=1)
self._build_left_panel()
self._build_right_panel()
def _build_left_panel(self):
panel = ctk.CTkScrollableFrame(self, fg_color=BG_CARD, corner_radius=0,
scrollbar_button_color=ACCENT)
panel.grid(row=0, column=0, sticky="nsew")
panel.grid_columnconfigure(0, weight=1)
# ── Encabezado
header = ctk.CTkFrame(panel, fg_color="transparent")
header.grid(row=0, column=0, sticky="ew", padx=20, pady=(24, 10))
ctk.CTkLabel(header, text="⬛ QR Studio",
font=("Segoe UI Black", 22), text_color=ACCENT).pack(anchor="w")
ctk.CTkLabel(header, text="Crea codigos QR personalizados",
font=("Segoe UI", 12), text_color=TEXT_MUTED).pack(anchor="w")
sep = ctk.CTkFrame(panel, height=2, fg_color=ACCENT, corner_radius=1)
sep.grid(row=1, column=0, sticky="ew", padx=20, pady=(0, 16))
row = 2
# ── Contenido
ctk.CTkLabel(panel, text="📝 Contenido del QR",
font=("Segoe UI Semibold", 13), text_color=TEXT_LIGHT,
anchor="w").grid(row=row, column=0, sticky="ew",
padx=20, pady=(0, 6))
row += 1
self.content_text = ctk.CTkTextbox(panel, height=90, corner_radius=10,
fg_color=BG_WIDGET, border_color=ACCENT,
border_width=1, text_color=TEXT_LIGHT,
font=("Segoe UI", 13))
self.content_text.grid(row=row, column=0, sticky="ew", padx=20)
self.content_text.insert("end", "https://usadosahora.com")
self.content_text.bind("", lambda e: self._schedule_preview())
row += 1
# ── Plantillas rapidas
quick = ctk.CTkFrame(panel, fg_color="transparent")
quick.grid(row=row, column=0, sticky="ew", padx=20, pady=(8, 16))
for i, (lbl, tmpl) in enumerate([
("🔗 URL", "https://"),
("📧 Email", "mailto:"),
("📞 Tel.", "tel:+"),
("📶 WiFi", "WIFI:T:WPA;S:MiRed;P:password;;"),
]):
b = ctk.CTkButton(quick, text=lbl, width=80, height=28,
fg_color=BG_WIDGET, hover_color=ACCENT,
text_color=TEXT_LIGHT, font=("Segoe UI", 11),
corner_radius=7,
command=lambda t=tmpl: self._set_template(t))
b.grid(row=0, column=i, padx=4)
row += 1
# ── Estilo de modulos
self._section(panel, row, "🎨 Estilo de modulos")
row += 1
self.drawer_var = ctk.StringVar(value="Redondeado")
seg = ctk.CTkSegmentedButton(panel, values=list(DRAWER_MAP.keys()),
variable=self.drawer_var,
font=("Segoe UI", 11),
selected_color=ACCENT,
selected_hover_color=ACCENT2,
unselected_color=BG_WIDGET,
command=lambda _: self._schedule_preview())
seg.grid(row=row, column=0, sticky="ew", padx=20)
row += 1
# ── Correccion de errores
self._section(panel, row, "🛡 Correccion de errores")
row += 1
self.error_var = ctk.StringVar(value="M - 15%")
err_menu = ctk.CTkOptionMenu(panel, values=list(ERROR_MAP.keys()),
variable=self.error_var,
fg_color=BG_WIDGET, button_color=ACCENT,
button_hover_color=ACCENT2,
text_color=TEXT_LIGHT,
font=("Segoe UI", 12), corner_radius=8,
command=lambda _: self._schedule_preview())
err_menu.grid(row=row, column=0, sticky="ew", padx=20)
row += 1
# ── Colores
self._section(panel, row, "🎨 Colores")
row += 1
self.fg_btn = ColorButton(panel, "Modulos", "#000000")
self.fg_btn.grid(row=row, column=0, sticky="ew", padx=20, pady=3)
self.fg_btn.on_change(lambda _: self._schedule_preview())
row += 1
self.bg_btn = ColorButton(panel, "Fondo ", "#FFFFFF")
self.bg_btn.grid(row=row, column=0, sticky="ew", padx=20, pady=3)
self.bg_btn.on_change(lambda _: self._schedule_preview())
row += 1
# ── Tamano de caja
self._section(panel, row, "📐 Tamano de caja (px)")
row += 1
self.box_size_var = ctk.IntVar(value=10)
ctk.CTkSlider(panel, from_=4, to=30,
variable=self.box_size_var, number_of_steps=26,
button_color=ACCENT, button_hover_color=ACCENT2,
progress_color=ACCENT,
command=lambda _: self._schedule_preview()
).grid(row=row, column=0, sticky="ew", padx=20)
row += 1
self.box_lbl = ctk.CTkLabel(panel, text="10 px", font=("Segoe UI", 11),
text_color=TEXT_MUTED, anchor="e")
self.box_lbl.grid(row=row, column=0, sticky="ew", padx=24, pady=(0, 4))
self.box_size_var.trace_add("write",
lambda *_: self.box_lbl.configure(text=f"{self.box_size_var.get()} px"))
row += 1
# ── Borde (quiet zone)
self._section(panel, row, "⬜ Borde (quiet zone)")
row += 1
self.border_var = ctk.IntVar(value=4)
ctk.CTkSlider(panel, from_=0, to=10,
variable=self.border_var, number_of_steps=10,
button_color=ACCENT, button_hover_color=ACCENT2,
progress_color=ACCENT,
command=lambda _: self._schedule_preview()
).grid(row=row, column=0, sticky="ew", padx=20)
row += 1
self.border_lbl = ctk.CTkLabel(panel, text="4", font=("Segoe UI", 11),
text_color=TEXT_MUTED, anchor="e")
self.border_lbl.grid(row=row, column=0, sticky="ew", padx=24, pady=(0, 4))
self.border_var.trace_add("write",
lambda *_: self.border_lbl.configure(text=str(self.border_var.get())))
row += 1
# ── Botones de accion
actions = ctk.CTkFrame(panel, fg_color="transparent")
actions.grid(row=row, column=0, sticky="ew", padx=20, pady=(16, 24))
actions.grid_columnconfigure((0, 1), weight=1)
ctk.CTkButton(actions, text="💾 Guardar PNG",
height=44, corner_radius=12,
fg_color=ACCENT, hover_color=ACCENT2,
font=("Segoe UI Semibold", 14),
command=self._save_png
).grid(row=0, column=0, sticky="ew", padx=(0, 6))
ctk.CTkButton(actions, text="📄 Guardar SVG",
height=44, corner_radius=12,
fg_color=BG_WIDGET, hover_color=ACCENT,
font=("Segoe UI Semibold", 14),
border_color=ACCENT, border_width=1,
command=self._save_svg
).grid(row=0, column=1, sticky="ew", padx=(6, 0))
def _build_right_panel(self):
panel = ctk.CTkFrame(self, fg_color=BG_DARK, corner_radius=0)
panel.grid(row=0, column=1, sticky="nsew")
panel.grid_rowconfigure(1, weight=1)
panel.grid_columnconfigure(0, weight=1)
# Status bar
self.status_var = ctk.StringVar(value="Listo")
status_bar = ctk.CTkFrame(panel, fg_color=BG_CARD, height=36,
corner_radius=0)
status_bar.grid(row=0, column=0, sticky="ew")
ctk.CTkLabel(status_bar, textvariable=self.status_var,
font=("Segoe UI", 12), text_color=TEXT_MUTED,
anchor="w").pack(side="left", padx=16)
# Preview area
preview_frame = ctk.CTkFrame(panel, fg_color=BG_DARK, corner_radius=0)
preview_frame.grid(row=1, column=0, sticky="nsew", padx=40, pady=40)
preview_frame.grid_rowconfigure(0, weight=1)
preview_frame.grid_columnconfigure(0, weight=1)
self.preview_label = ctk.CTkLabel(preview_frame, text="⏳ Generando...",
fg_color=BG_CARD, corner_radius=20,
text_color=TEXT_MUTED,
font=("Segoe UI", 16))
self.preview_label.grid(row=0, column=0, sticky="nsew")
# Boton de copiar
ctk.CTkButton(panel, text="📋 Copiar al portapapeles",
height=38, corner_radius=10,
fg_color=BG_CARD, hover_color=BG_WIDGET,
font=("Segoe UI", 12), text_color=TEXT_MUTED,
command=self._copy_to_clipboard
).grid(row=2, column=0, sticky="ew", padx=40, pady=(0, 20))
# ── Helpers ──────────────────────────────────────────────────────────────
def _section(self, parent, row: int, text: str):
ctk.CTkLabel(parent, text=text, font=("Segoe UI Semibold", 13),
text_color=TEXT_LIGHT, anchor="w").grid(
row=row, column=0, sticky="ew", padx=20, pady=(14, 4))
def _set_template(self, tmpl: str):
self.content_text.delete("1.0", "end")
self.content_text.insert("end", tmpl)
self._schedule_preview()
def _get_content(self) -> str:
return self.content_text.get("1.0", "end").strip()
# ── Generacion del QR ─────────────────────────────────────────────────────
def _schedule_preview(self, _=None):
"""Lanza la generacion en un hilo para no bloquear la UI."""
if self._gen_thread and self._gen_thread.is_alive():
return
self._gen_thread = threading.Thread(target=self._generate, daemon=True)
self._gen_thread.start()
def _generate(self):
self.after(0, lambda: self.status_var.set("⏳ Generando..."))
try:
content = self._get_content()
if not content:
self.after(0, lambda: self.status_var.set("⚠ Sin contenido"))
return
drawer = DRAWER_MAP[self.drawer_var.get()]()
ec_level = ERROR_MAP[self.error_var.get()]
box_size = self.box_size_var.get()
border = self.border_var.get()
fg_rgb = hex_to_rgb(self.fg_btn.color)
bg_rgb = hex_to_rgb(self.bg_btn.color)
qr = qrcode.QRCode(
error_correction=ec_level,
box_size=box_size,
border=border,
)
qr.add_data(content)
qr.make(fit=True)
img = qr.make_image(
image_factory=StyledPilImage,
module_drawer=drawer,
color_mask=SolidFillColorMask(
front_color=fg_rgb,
back_color=bg_rgb
)
)
self._qr_image = img.convert("RGBA")
version = qr.version
modules = version * 4 + 17
self.after(0, self._update_preview)
self.after(0, lambda: self.status_var.set(
f"✅ QR listo · version {version} · {modules}x{modules} modulos"))
except Exception as e:
err = str(e)
self.after(0, lambda: self.status_var.set(f"❌ Error: {err}"))
def _update_preview(self):
if self._qr_image is None:
return
pw = self.preview_label.winfo_width() or 400
ph = self.preview_label.winfo_height() or 400
size = min(pw, ph) - 40
if size < 80:
size = 360
img_resized = self._qr_image.copy()
img_resized.thumbnail((size, size), Image.LANCZOS)
ctk_img = ctk.CTkImage(light_image=img_resized,
dark_image=img_resized,
size=(size, size))
self._preview_tk = ctk_img
self.preview_label.configure(image=self._preview_tk, text="")
# ── Guardado ─────────────────────────────────────────────────────────────
def _save_png(self):
if self._qr_image is None:
messagebox.showwarning("Aviso", "Primero genera un QR.")
return
path = filedialog.asksaveasfilename(
defaultextension=".png",
filetypes=[("PNG image", "*.png"), ("JPEG image", "*.jpg"),
("BMP image", "*.bmp")],
title="Guardar codigo QR",
initialfile="mi_qr.png"
)
if path:
img = self._qr_image
if path.lower().endswith((".jpg", ".jpeg")):
img = img.convert("RGB")
img.save(path)
self.status_var.set(f"💾 Guardado en {os.path.basename(path)}")
messagebox.showinfo("Guardado", f"QR guardado en:\n{path}")
def _save_svg(self):
"""Genera y guarda un SVG usando qrcode."""
content = self._get_content()
if not content:
messagebox.showwarning("Aviso", "Sin contenido para generar el QR.")
return
path = filedialog.asksaveasfilename(
defaultextension=".svg",
filetypes=[("SVG image", "*.svg")],
title="Guardar QR como SVG",
initialfile="mi_qr.svg"
)
if not path:
return
try:
import qrcode.image.svg as qr_svg
factory = qr_svg.SvgPathImage
ec_level = ERROR_MAP[self.error_var.get()]
img = qrcode.make(
content,
image_factory=factory,
error_correction=ec_level,
)
img.save(path)
self.status_var.set(f"📄 SVG guardado en {os.path.basename(path)}")
messagebox.showinfo("Guardado", f"SVG guardado en:\n{path}")
except Exception as e:
messagebox.showerror("Error", str(e))
def _copy_to_clipboard(self):
"""Copia la imagen QR al portapapeles (Windows)."""
if self._qr_image is None:
messagebox.showwarning("Aviso", "Primero genera un QR.")
return
try:
import win32clipboard
from io import BytesIO
output = BytesIO()
img = self._qr_image.convert("RGB")
img.save(output, "BMP")
data = output.getvalue()[14:]
output.close()
win32clipboard.OpenClipboard()
win32clipboard.EmptyClipboard()
win32clipboard.SetClipboardData(win32clipboard.CF_DIB, data)
win32clipboard.CloseClipboard()
self.status_var.set("📋 Imagen copiada al portapapeles!")
except ImportError:
tmp = os.path.join(os.environ.get("TEMP", "."), "_qr_temp.png")
self._qr_image.convert("RGB").save(tmp)
self.status_var.set("⚠ pywin32 no instalado. QR guardado en TEMP")
messagebox.showinfo(
"Portapapeles",
f"pywin32 no esta instalado.\nQR guardado temporalmente en:\n{tmp}"
)
if __name__ == "__main__":
app = QRApp()
app.mainloop()