🎨 Developer Cookbook - FASE 7: UX/UI para Desarrolladores

Principios de diseño y usabilidad para crear interfaces que deleitan


📚 Tabla de Contenidos

  1. Receta 7.6: Principios de Diseño Centrado en el Usuario
  2. Receta 7.7: Psicología del Usuario - Cognitive Load
  3. Receta 7.8: Prototipado Rápido con Figma
  4. Receta 7.9: Accesibilidad (WCAG)

Receta 7.6: Principios de Diseño Centrado en el Usuario

¿Qué es? Diseñar productos pensando en el usuario real, no en lo que asumimos que necesitan.

5 Principios fundamentales:

class UXPrinciples:
    """Principios de UX que todo developer debe conocer"""

    @staticmethod
    def principle_1_visibility():
        """Visibility of System Status"""
        print("1️⃣ VISIBILITY OF SYSTEM STATUS")
        print("   El usuario siempre debe saber qué está pasando\n")

        print("   ❌ MAL:")
        print("   [Submit button] → *nada pasa por 3 segundos* → Success!")

        print("\n   ✅ BIEN:")
        print("   [Submit button] → [Loading spinner] → [Progress: 50%] → Success!")

        code = '''
        # Ejemplo en código
        def upload_file(file):
            # ❌ MAL: No feedback
            process_file(file)
            return "Done"

            # ✅ BIEN: Progress feedback
            total_steps = 5
            update_progress(0, total_steps, "Uploading...")
            upload_to_s3(file)

            update_progress(1, total_steps, "Scanning for viruses...")
            scan_file(file)

            update_progress(2, total_steps, "Generating thumbnail...")
            create_thumbnail(file)

            update_progress(3, total_steps, "Updating database...")
            save_to_db(file)

            update_progress(4, total_steps, "Complete!")
            return "Done"
        '''
        print(f"\n{code}\n")

    @staticmethod
    def principle_2_real_world():
        """Match Between System and Real World"""
        print("2️⃣ MATCH BETWEEN SYSTEM AND REAL WORLD")
        print("   Usa lenguaje que los usuarios entienden, no jerga técnica\n")

        print("   ❌ MAL (Developer language):")
        print("   'Error 429: Rate limit exceeded. Retry after exponential backoff'")

        print("\n   ✅ BIEN (Human language):")
        print("   'Whoa, slow down! You are making too many requests. Try again in 1 minute.'")

        print("\n   ❌ MAL (Buttons):")
        print("   [Confirm] [Cancel]")

        print("\n   ✅ BIEN (Descriptive):")
        print("   [Delete 23 files] [Keep files]")

        code = '''
        # Mensajes de error
        errors = {
            # ❌ Developer-speak
            "bad": "HTTP 400 Bad Request: Invalid JSON payload",

            # ✅ Human-speak
            "good": "Oops! We couldn't understand your request. Please check your input."
        }

        # Confirmaciones
        confirmations = {
            # ❌ Generic
            "bad": "Are you sure?",

            # ✅ Specific
            "good": "Delete 156 customer records? This cannot be undone."
        }
        '''
        print(f"{code}\n")

    @staticmethod
    def principle_3_user_control():
        """User Control and Freedom"""
        print("3️⃣ USER CONTROL AND FREEDOM")
        print("   Los usuarios cometen errores. Dale 'undo' fácil\n")

        print("   ✅ Gmail: 'Undo send' por 5 segundos")
        print("   ✅ Slack: Edit/delete messages")
        print("   ✅ Stripe: Refund con 1 click")

        code = '''
        # Pattern: Soft delete con undo
        class TaskManager:
            def __init__(self):
                self.tasks = []
                self.deleted_tasks = []  # Trash bin

            def delete_task(self, task_id: int):
                """Soft delete con undo window"""
                task = self.find_task(task_id)
                task.deleted_at = datetime.now()
                task.undo_deadline = datetime.now() + timedelta(seconds=10)

                self.deleted_tasks.append(task)
                self.tasks.remove(task)

                print(f"✅ Task deleted. [Undo] (10 seconds)")

                # Schedule permanent delete
                self.schedule_permanent_delete(task, delay=10)

            def undo_delete(self, task_id: int):
                """Restore deleted task"""
                task = self.find_deleted_task(task_id)

                if datetime.now() > task.undo_deadline:
                    print("❌ Too late to undo")
                    return False

                self.tasks.append(task)
                self.deleted_tasks.remove(task)
                print("↩️  Task restored")
                return True
        '''
        print(f"{code}\n")

    @staticmethod
    def principle_4_consistency():
        """Consistency and Standards"""
        print("4️⃣ CONSISTENCY AND STANDARDS")
        print("   Usa patrones que los usuarios ya conocen\n")

        print("   ✅ Standards:")
        print("   • 'X' = close")
        print("   • '🔍' = search")
        print("   • Logo top-left lleva a home")
        print("   • Cmd+S / Ctrl+S = save")
        print("   • Red = danger, Green = success")

        print("\n   ❌ MAL: Inventar tus propios patterns")
        print("   • '?' button para cerrar")
        print("   • Search icon es una lupa azul rotada 45°")
        print("   • Logo en bottom-right")

        code = '''
        # Design System: Mantén consistencia
        class DesignSystem:
            # Colors (use always the same)
            COLORS = {
                'primary': '#3B82F6',      # Blue
                'success': '#10B981',      # Green
                'danger': '#EF4444',       # Red
                'warning': '#F59E0B',      # Orange
                'info': '#6366F1'          # Indigo
            }

            # Button sizes (don't mix)
            BUTTON_SIZES = {
                'small': 'px-3 py-1.5 text-sm',
                'medium': 'px-4 py-2 text-base',
                'large': 'px-6 py-3 text-lg'
            }

            # Spacing (use system, not random values)
            SPACING = [0, 4, 8, 12, 16, 24, 32, 48, 64]  # px

            # Don't do this:
            # margin: 13px  ❌ (random)
            # margin: 16px  ✅ (from system)
        '''
        print(f"{code}\n")

    @staticmethod
    def principle_5_error_prevention():
        """Error Prevention"""
        print("5️⃣ ERROR PREVENTION")
        print("   Mejor prevenir errores que mostrar buenos mensajes de error\n")

        print("   ✅ Strategies:")
        print("   • Disable invalid actions")
        print("   • Confirmation dialogs para acciones destructivas")
        print("   • Input validation in real-time")
        print("   • Defaults seguros")

        code = '''
        # Ejemplo: Prevenir errores en forms
        class SmartForm:
            def validate_email_realtime(self, email: str) -> dict:
                """Validar mientras el usuario escribe"""

                if not email:
                    return {'valid': None, 'message': ''}

                if '@' not in email:
                    return {
                        'valid': False,
                        'message': '⚠️ Email debe contener @'
                    }

                if email.count('@') > 1:
                    return {
                        'valid': False,
                        'message': '⚠️ Solo un @ permitido'
                    }

                domain = email.split('@')[1]
                if '.' not in domain:
                    return {
                        'valid': False,
                        'message': '⚠️ Domain inválido (ej: example.com)'
                    }

                return {
                    'valid': True,
                    'message': '✅ Email válido'
                }

            def prevent_destructive_action(self, action: str):
                """Require confirmation para acciones peligrosas"""

                dangerous_actions = ['delete_all', 'reset_database', 'cancel_subscription']

                if action in dangerous_actions:
                    # Require typing "DELETE" to confirm
                    confirmation = input(f"Type 'DELETE' to confirm {action}: ")

                    if confirmation != 'DELETE':
                        print("❌ Action cancelled")
                        return False

                return True
        '''
        print(f"{code}\n")

    @staticmethod
    def print_all_principles():
        """Imprimir todos los principios"""
        print("\n" + "🎨 " * 20)
        print("UX PRINCIPLES FOR DEVELOPERS")
        print("🎨 " * 20 + "\n")

        UXPrinciples.principle_1_visibility()
        print("-" * 80 + "\n")
        UXPrinciples.principle_2_real_world()
        print("-" * 80 + "\n")
        UXPrinciples.principle_3_user_control()
        print("-" * 80 + "\n")
        UXPrinciples.principle_4_consistency()
        print("-" * 80 + "\n")
        UXPrinciples.principle_5_error_prevention()

UXPrinciples.print_all_principles()

Receta 7.7: Psicología del Usuario - Cognitive Load

¿Qué es? Cognitive load = cuánta “energía mental” requiere tu interfaz. Menos load = mejor UX.

3 tipos de Cognitive Load:

class CognitiveLoadPrinciples:
    """Reducir carga cognitiva del usuario"""

    @staticmethod
    def intrinsic_load():
        """Complexity inherente a la tarea"""
        print("1️⃣ INTRINSIC LOAD (No puedes cambiar esto)")
        print("   Es la complejidad inherente de la tarea")
        print("   Ejemplo: Hacer taxes es inherentemente complejo\n")

        print("   ✅ Qué puedes hacer:")
        print("   • Divide tareas complejas en pasos simples")
        print("   • Usa progressive disclosure (muestra info gradualmente)")

        code = '''
        # ❌ MAL: Todo de golpe
        def create_account_form():
            return {
                'fields': [
                    'name', 'email', 'password', 'confirm_password',
                    'company', 'role', 'team_size', 'industry',
                    'phone', 'country', 'timezone', 'currency',
                    'billing_address', 'payment_method', 'promo_code'
                ]
            }
            # 15 campos = usuario overwhelmed

        # ✅ BIEN: Multi-step wizard
        def create_account_wizard():
            return {
                'step_1': ['name', 'email', 'password'],           # Esencial
                'step_2': ['company', 'role', 'team_size'],       # Contexto
                'step_3': ['payment_method', 'billing_address']   # Billing
            }
            # 3 steps × 3 campos = mucho más fácil
        '''
        print(f"{code}\n")

    @staticmethod
    def extraneous_load():
        """Complexity por mal diseño (PUEDES eliminar esto)"""
        print("2️⃣ EXTRANEOUS LOAD (Elimina esto)")
        print("   Complejidad causada por mal diseño\n")

        print("   ❌ Causes:")
        print("   • UI cluttered (demasiados elementos)")
        print("   • Inconsistency")
        print("   • Jerga técnica")
        print("   • Pasos innecesarios")

        code = '''
        # ❌ MAL: Extraneous load
        class BadCheckout:
            def complete_purchase(self, cart):
                # Step 1: Review cart
                show_cart(cart)

                # Step 2: Create account (WHY?? Already have cart!)
                require_account_creation()

                # Step 3: Verify email (FRICTION!)
                send_verification_email()
                wait_for_verification()

                # Step 4: Enter shipping
                get_shipping_info()

                # Step 5: Enter billing (why separate from shipping?)
                get_billing_info()

                # Step 6: Review AGAIN
                show_cart(cart)  # We already did this!

                # Step 7: Payment
                process_payment()

                # Total: 7 steps, 3 unnecessary

        # ✅ BIEN: Minimal load
        class GoodCheckout:
            def complete_purchase(self, cart):
                # Step 1: Shipping + Billing (combined)
                info = get_shipping_and_billing()

                # Step 2: Payment
                process_payment(cart, info)

                # Optional: Create account AFTER purchase (convert guest)
                offer_account_creation()

                # Total: 2 steps (or 1.5 if guest)
        '''
        print(f"{code}\n")

    @staticmethod
    def germane_load():
        """Complexity que ayuda al aprendizaje"""
        print("3️⃣ GERMANE LOAD (Esto es bueno)")
        print("   Complejidad que ayuda al usuario a aprender y mejorar\n")

        print("   ✅ Examples:")
        print("   • Tooltips que enseñan keyboard shortcuts")
        print("   • Onboarding interactivo")
        print("   • Context-aware help")

        code = '''
        # ✅ BIEN: Germane load que enseña
        class SmartOnboarding:
            def show_feature_tooltip(self, feature: str, first_use: bool):
                """Mostrar tooltip solo la primera vez"""

                if first_use:
                    tooltips = {
                        'search': {
                            'message': 'Pro tip: Use Cmd+K to search anywhere',
                            'shortcut': '⌘K',
                            'helpful': True  # User learns and gets faster
                        },
                        'bulk_edit': {
                            'message': 'Select multiple with Shift+Click',
                            'visual': show_animated_gif('shift_click.gif'),
                            'helpful': True
                        }
                    }

                    return tooltips.get(feature)

                return None  # Don't show tooltip again

            def contextual_help(self, user_action: str):
                """Help basado en qué está tratando de hacer"""

                if user_action == 'confused_about_pricing':
                    return {
                        'help': 'Our starter plan is best for teams under 10',
                        'comparison_table': show_plan_comparison(),
                        'video': '2-min explainer video'
                    }

                if user_action == 'trying_advanced_feature':
                    return {
                        'help': 'This feature requires Pro plan',
                        'upgrade_cta': 'Upgrade to unlock',
                        'trial': 'Or start 14-day free trial'
                    }
        '''
        print(f"{code}\n")

    @staticmethod
    def print_all():
        print("\n" + "🧠 " * 20)
        print("COGNITIVE LOAD PRINCIPLES")
        print("🧠 " * 20 + "\n")

        CognitiveLoadPrinciples.intrinsic_load()
        print("-" * 80 + "\n")
        CognitiveLoadPrinciples.extraneous_load()
        print("-" * 80 + "\n")
        CognitiveLoadPrinciples.germane_load()

CognitiveLoadPrinciples.print_all()

Heurísticas para reducir cognitive load:

class CognitiveLoadHeuristics:
    """Técnicas prácticas para reducir load"""

    @staticmethod
    def heuristic_1_chunking():
        """Agrupa información relacionada"""
        print("📦 CHUNKING - Agrupa info relacionada")
        print("   Humanos recuerdan 7±2 items. Usa chunks.\n")

        print("   ❌ MAL:")
        print("   Phone: 5551234567")

        print("\n   ✅ BIEN:")
        print("   Phone: (555) 123-4567")
        print("   Credit card: 1234 5678 9012 3456")
        print("   SSN: 123-45-6789\n")

        code = '''
        def format_credit_card(number: str) -> str:
            """Formato en chunks de 4"""
            # ❌ MAL: 1234567890123456
            # ✅ BIEN: 1234 5678 9012 3456
            return ' '.join([number[i:i+4] for i in range(0, len(number), 4)])
        '''
        print(f"{code}\n")

    @staticmethod
    def heuristic_2_defaults():
        """Buenos defaults reducen decisiones"""
        print("🎯 SMART DEFAULTS - Reduce decisiones")
        print("   80% de usuarios nunca cambian defaults\n")

        code = '''
        # ❌ MAL: Require todas las decisiones
        class BadSignup:
            def create_account(self):
                timezone = ask_user_timezone()  # They don't know!
                date_format = ask_date_format()  # They don't care!
                currency = ask_currency()        # Obvious from country!
                theme = ask_theme()             # Just pick one!

        # ✅ BIEN: Smart defaults basados en contexto
        class GoodSignup:
            def create_account(self, ip_address: str):
                # Detect desde IP
                country = geolocate(ip_address)

                defaults = {
                    'timezone': infer_timezone(country),
                    'currency': country_currency_map[country],
                    'date_format': country_date_format[country],
                    'theme': 'light',  # Most popular
                    'language': detect_browser_language()
                }

                # User puede cambiar después si quiere
                return create_with_defaults(defaults)
        '''
        print(f"{code}\n")

    @staticmethod
    def heuristic_3_progressive_disclosure():
        """Muestra solo lo necesario ahora"""
        print("📖 PROGRESSIVE DISCLOSURE - Show gradually")
        print("   No muestres todo de golpe\n")

        code = '''
        # ❌ MAL: Everything visible always
        def settings_page():
            return show_all_settings([
                'account', 'billing', 'notifications', 'privacy',
                'security', 'integrations', 'api_keys', 'webhooks',
                'team', 'permissions', 'audit_log', 'data_export',
                'danger_zone'
            ])  # 13 sections = overwhelming

        # ✅ BIEN: Categorized + collapsed by default
        def settings_page():
            return {
                'visible': ['account', 'notifications'],  # Common
                'collapsed': {
                    'Advanced': ['api_keys', 'webhooks'],
                    'Security': ['privacy', 'security', 'audit_log'],
                    'Billing': ['billing', 'data_export'],
                    'Danger Zone': ['danger_zone']  # Hidden by default
                }
            }
        '''
        print(f"{code}\n")

    @staticmethod
    def heuristic_4_recognition_vs_recall():
        """Recognition > Recall"""
        print("👁️  RECOGNITION > RECALL")
        print("   Más fácil reconocer que recordar\n")

        print("   ❌ MAL (Recall): Dropdown vacío")
        print("   Select country: [________]")

        print("\n   ✅ BIEN (Recognition): Dropdown con opciones")
        print("   Select country: [🇺🇸 United States ▼]")
        print("                    🇲🇽 Mexico")
        print("                    🇨🇦 Canada\n")

        code = '''
        # ❌ MAL: Free text input (recall required)
        def search_products():
            query = input("Search: ")  # User must remember exact name

        # ✅ BIEN: Autocomplete (recognition)
        def search_products_autocomplete():
            query = input("Search: ")
            if len(query) >= 2:
                suggestions = get_suggestions(query)
                show_dropdown(suggestions)  # User recognizes what they want
        '''
        print(f"{code}\n")

    @staticmethod
    def print_all():
        print("\n" + "💡 " * 20)
        print("COGNITIVE LOAD REDUCTION HEURISTICS")
        print("💡 " * 20 + "\n")

        CognitiveLoadHeuristics.heuristic_1_chunking()
        CognitiveLoadHeuristics.heuristic_2_defaults()
        CognitiveLoadHeuristics.heuristic_3_progressive_disclosure()
        CognitiveLoadHeuristics.heuristic_4_recognition_vs_recall()

CognitiveLoadHeuristics.print_all()

Receta 7.8: Prototipado Rápido con Figma

¿Qué es? Crear mockups interactivos ANTES de escribir código. 10 minutos en Figma te ahorran 10 horas de código.

Workflow: Idea → Prototype → Test → Code

class PrototypingWorkflow:
    """Proceso para validar UI antes de programar"""

    @staticmethod
    def step_1_wireframes():
        """Low-fidelity wireframes (papel o Figma)"""
        print("STEP 1: WIREFRAMES (10-30 min)")
        print("=" * 60)
        print("Goal: Validar estructura y flujo\n")

        print("Tools:")
        print("• Papel + lápiz (más rápido)")
        print("• Figma wireframe kit")
        print("• Balsamiq\n")

        print("Lo que DEBES incluir:")
        print("✅ Layout básico (header, sidebar, content)")
        print("✅ Navigation flow (qué pasa cuando click)")
        print("✅ Key user actions (buttons, forms)")

        print("\nLo que NO necesitas:")
        print("❌ Colores")
        print("❌ Fonts específicas")
        print("❌ Imágenes reales")
        print("❌ Copy final\n")

        example = '''
        Ejemplo wireframe (ASCII art):

        +------------------------------------------+
        | Logo    Search...          Login  Signup|
        +------------------------------------------+
        | Dashboard | Projects | Settings          |
        +------------------------------------------+
        |                                          |
        |  [+ New Project]                         |
        |                                          |
        |  +------------------+                    |
        |  | Project A        |                    |
        |  | 23 tasks         |                    |
        |  | [View] [Delete]  |                    |
        |  +------------------+                    |
        |                                          |
        |  +------------------+                    |
        |  | Project B        |                    |
        |  | 45 tasks         |                    |
        |  | [View] [Delete]  |                    |
        |  +------------------+                    |
        |                                          |
        +------------------------------------------+
        '''
        print(example)

    @staticmethod
    def step_2_mockups():
        """High-fidelity mockups con diseño visual"""
        print("\nSTEP 2: HIGH-FIDELITY MOCKUPS (1-2 hours)")
        print("=" * 60)
        print("Goal: Diseño visual final antes de código\n")

        print("Checklist:")
        print("✅ Colors (usa design system)")
        print("✅ Typography (2-3 fonts máximo)")
        print("✅ Spacing (8px grid)")
        print("✅ Icons (usa icon library)")
        print("✅ Real copy (no lorem ipsum)")
        print("✅ Responsive (desktop + mobile)\n")

        print("Figma plugins útiles:")
        print("• Iconify - 100k+ icons gratis")
        print("• Unsplash - Stock photos")
        print("• Content Reel - Real data")
        print("• Stark - Accessibility checker\n")

    @staticmethod
    def step_3_prototype():
        """Interactive prototype para user testing"""
        print("\nSTEP 3: INTERACTIVE PROTOTYPE (30 min)")
        print("=" * 60)
        print("Goal: Simular la app para testing\n")

        print("Figma prototype features:")
        print("• Click flows (button → new screen)")
        print("• Hover states")
        print("• Animations")
        print("• Mobile gestures (swipe, scroll)\n")

        print("Example flows to prototype:")
        print("1. User signup flow")
        print("2. Main user journey (ej: create → edit → delete)")
        print("3. Error states (what happens when error?)")
        print("4. Empty states (what shows when no data?)\n")

    @staticmethod
    def step_4_user_testing():
        """Test con 5 usuarios reales"""
        print("\nSTEP 4: USER TESTING (2-3 hours total)")
        print("=" * 60)
        print("Goal: Encontrar problemas ANTES de programar\n")

        print("Process:")
        print("1. Recruit 5 users (friends, Twitter, UserTesting.com)")
        print("2. Give them task: 'Sign up and create your first project'")
        print("3. Watch them (Zoom screen share)")
        print("4. Take notes (don't help them!)")
        print("5. Ask questions after\n")

        print("Red flags 🚩:")
        print("• User clicks wrong thing")
        print("• User says 'I don't understand'")
        print("• User gives up")
        print("• User asks 'What does this do?'")
        print("• Takes >2 minutes for simple task\n")

        print("If 3+ users struggle with same thing → FIX BEFORE CODING")

    @staticmethod
    def step_5_handoff_to_dev():
        """Developer handoff"""
        print("\nSTEP 5: DEVELOPER HANDOFF")
        print("=" * 60)
        print("Goal: Hacer fácil implementar el diseño\n")

        print("Figma Dev Mode features:")
        print("✅ Inspect - Ver CSS automáticamente")
        print("✅ Export assets (SVG, PNG)")
        print("✅ Design tokens (colors, spacing)")
        print("✅ Component specs\n")

        print("Developer-friendly checklist:")
        print("• Todas las pantallas nombradas claramente")
        print("• Components organizados")
        print("• Hover/active states documentados")
        print("• Edge cases diseñados (loading, error, empty)")
        print("• Responsive breakpoints especificados\n")

    @staticmethod
    def print_workflow():
        print("\n" + "🎨 " * 20)
        print("FIGMA PROTOTYPING WORKFLOW")
        print("🎨 " * 20 + "\n")

        PrototypingWorkflow.step_1_wireframes()
        PrototypingWorkflow.step_2_mockups()
        PrototypingWorkflow.step_3_prototype()
        PrototypingWorkflow.step_4_user_testing()
        PrototypingWorkflow.step_5_handoff_to_dev()

        print("\n" + "=" * 60)
        print("💡 ROI de Prototyping:")
        print("  Time invested: 3-4 hours")
        print("  Time saved: 10-20 hours de código descartado")
        print("  User satisfaction: ⬆️ 40%")
        print("=" * 60)

PrototypingWorkflow.print_workflow()

Receta 7.9: Accesibilidad (WCAG)

¿Qué es? Hacer tu producto usable por personas con discapacidades. No es opcional, es un requirement (y en algunos países, legal obligation).

WCAG Principles: POUR

class WCAGPrinciples:
    """Web Content Accessibility Guidelines"""

    @staticmethod
    def perceivable():
        """P - Perceivable: Usuarios deben poder percibir el contenido"""
        print("1️⃣ PERCEIVABLE")
        print("   La información debe ser presentable de forma que usuarios puedan percibirla\n")

        print("✅ Text Alternatives (images, icons):")
        code = '''
        <!-- ❌ MAL: No alt text -->
        <img src="logo.png">

        <!-- ✅ BIEN: Alt text descriptivo -->
        <img src="logo.png" alt="Acme Corp Logo">

        <!-- ✅ BIEN: Decorative image (alt vacío) -->
        <img src="decoration.png" alt="" role="presentation">

        <!-- ❌ MAL: Icon buttons sin label -->
        <button><i class="icon-trash"></i></button>

        <!-- ✅ BIEN: Icon con aria-label -->
        <button aria-label="Delete item">
            <i class="icon-trash" aria-hidden="true"></i>
        </button>
        '''
        print(code)

        print("\n✅ Color Contrast (mínimo 4.5:1):")
        code2 = '''
        # Calcular contrast ratio
        def calculate_contrast_ratio(color1_rgb, color2_rgb):
            def luminance(rgb):
                # Convert to linear RGB
                def adjust(c):
                    c = c / 255.0
                    return c / 12.92 if c <= 0.03928 else ((c + 0.055) / 1.055) ** 2.4

                r, g, b = [adjust(c) for c in rgb]
                return 0.2126 * r + 0.7152 * g + 0.0722 * b

            l1 = luminance(color1_rgb)
            l2 = luminance(color2_rgb)

            lighter = max(l1, l2)
            darker = min(l1, l2)

            return (lighter + 0.05) / (darker + 0.05)

        # Example
        white = (255, 255, 255)
        light_gray = (170, 170, 170)

        ratio = calculate_contrast_ratio(white, light_gray)
        print(f"Contrast: {ratio:.2f}:1")

        if ratio >= 4.5:
            print("✅ WCAG AA compliant")
        elif ratio >= 3.0:
            print("⚠️  WCAG AA compliant for large text only")
        else:
            print("❌ Does not meet WCAG AA")
        '''
        print(code2)
        print()

    @staticmethod
    def operable():
        """O - Operable: Usuarios deben poder operar la interfaz"""
        print("\n2️⃣ OPERABLE")
        print("   La interfaz debe ser operable con teclado y otros input methods\n")

        print("✅ Keyboard Navigation:")
        code = '''
        <!-- ❌ MAL: onClick en div (no keyboard accessible) -->
        <div onclick="doSomething()">Click me</div>

        <!-- ✅ BIEN: Usar button (inherently keyboard accessible) -->
        <button onclick="doSomething()">Click me</button>

        <!-- ✅ BIEN: Custom focus styles -->
        <style>
        button:focus {
            outline: 3px solid #3B82F6;  /* Visible focus indicator */
            outline-offset: 2px;
        }

        /* ❌ NUNCA hagas esto: */
        *:focus {
            outline: none;  /* Elimina focus = bad accessibility */
        }
        </style>

        <!-- ✅ BIEN: Skip to content link -->
        <a href="#main-content" class="skip-link">
            Skip to main content
        </a>
        '''
        print(code)

        print("\n✅ Focus Management:")
        code2 = '''
        # Ejemplo: Focus trap en modal
        class AccessibleModal:
            def __init__(self):
                self.focusable_elements = []
                self.first_focusable = None
                self.last_focusable = None

            def open_modal(self):
                # 1. Store element that opened modal
                self.trigger_element = document.activeElement

                # 2. Get all focusable elements in modal
                self.focusable_elements = modal.querySelectorAll(
                    'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
                )

                self.first_focusable = self.focusable_elements[0]
                self.last_focusable = self.focusable_elements[-1]

                # 3. Focus first element
                self.first_focusable.focus()

                # 4. Trap focus inside modal
                modal.addEventListener('keydown', self.handle_tab)

            def handle_tab(self, event):
                """Keep focus inside modal"""
                if event.key == 'Tab':
                    if event.shiftKey:  # Shift + Tab
                        if document.activeElement == self.first_focusable:
                            event.preventDefault()
                            self.last_focusable.focus()
                    else:  # Tab
                        if document.activeElement == self.last_focusable:
                            event.preventDefault()
                            self.first_focusable.focus()

            def close_modal(self):
                # Return focus to trigger element
                self.trigger_element.focus()
        '''
        print(code2)
        print()

    @staticmethod
    def understandable():
        """U - Understandable: Usuarios deben poder entender la información"""
        print("\n3️⃣ UNDERSTANDABLE")
        print("   El contenido y la operación deben ser comprensibles\n")

        print("✅ Error Prevention & Recovery:")
        code = '''
        <!-- ❌ MAL: Error genérico -->
        <span class="error">Invalid input</span>

        <!-- ✅ BIEN: Error descriptivo con solution -->
        <div role="alert" class="error">
            <p id="email-error">
                Email must include @ symbol.
                Example: user@example.com
            </p>
        </div>
        <input
            type="email"
            aria-describedby="email-error"
            aria-invalid="true"
        >

        <!-- ✅ BIEN: Form validation con clear instructions -->
        <label for="password">
            Password
            <span class="hint">Must be 8+ characters with 1 number</span>
        </label>
        <input
            type="password"
            id="password"
            aria-describedby="password-hint"
            minlength="8"
            required
        >
        <span id="password-hint" class="sr-only">
            Password requirements: minimum 8 characters including at least 1 number
        </span>
        '''
        print(code)

        print("\n✅ Consistent Navigation:")
        code2 = '''
        # ✅ BIEN: Consistent heading structure
        # <h1>Page Title</h1>              # Only one h1

        # <h2>Section 1</h2>
        #     <h3>Subsection 1.1</h3>
        #     <h3>Subsection 1.2</h3>

        # <h2>Section 2</h2>
        #     <h3>Subsection 2.1</h3>

        # ❌ MAL: Skipping heading levels
        # <h1>Page Title</h1>
        # <h4>Subsection</h4>  # Missing h2, h3
        '''
        print(code2)
        print()

    @staticmethod
    def robust():
        """R - Robust: Compatible con assistive technologies"""
        print("\n4️⃣ ROBUST")
        print("   El contenido debe ser compatible con screen readers y otras AT\n")

        print("✅ Semantic HTML:")
        code = '''
        <!-- ❌ MAL: Div soup -->
        <div class="navigation">
            <div class="nav-item">Home</div>
            <div class="nav-item">About</div>
        </div>

        <div class="main-content">
            <div class="article">
                <div class="title">Article Title</div>
                <div class="text">Article content...</div>
            </div>
        </div>

        <!-- ✅ BIEN: Semantic HTML -->
        <nav aria-label="Main navigation">
            <ul>
                <li><a href="/">Home</a></li>
                <li><a href="/about">About</a></li>
            </ul>
        </nav>

        <main>
            <article>
                <h2>Article Title</h2>
                <p>Article content...</p>
            </article>
        </main>
        '''
        print(code)

        print("\n✅ ARIA roles & labels:")
        code2 = '''
        <!-- Custom components need ARIA -->

        <!-- ✅ BIEN: Custom dropdown -->
        <div class="dropdown">
            <button
                aria-haspopup="true"
                aria-expanded="false"
                aria-controls="dropdown-menu"
                id="dropdown-button"
            >
                Options
            </button>

            <ul
                role="menu"
                id="dropdown-menu"
                aria-labelledby="dropdown-button"
                hidden
            >
                <li role="menuitem">
                    <a href="/edit">Edit</a>
                </li>
                <li role="menuitem">
                    <a href="/delete">Delete</a>
                </li>
            </ul>
        </div>

        <!-- ✅ BIEN: Loading state -->
        <div
            role="status"
            aria-live="polite"
            aria-busy="true"
        >
            Loading data...
        </div>

        <!-- Screen reader announces updates -->
        <div
            role="alert"
            aria-live="assertive"
        >
            Form submitted successfully!
        </div>
        '''
        print(code2)
        print()

    @staticmethod
    def print_all():
        print("\n" + "♿ " * 20)
        print("WCAG ACCESSIBILITY PRINCIPLES (POUR)")
        print("♿ " * 20)

        WCAGPrinciples.perceivable()
        WCAGPrinciples.operable()
        WCAGPrinciples.understandable()
        WCAGPrinciples.robust()

        print("\n" + "=" * 70)
        print("🧰 Testing Tools:")
        print("  • axe DevTools (Chrome extension)")
        print("  • WAVE (Web Accessibility Evaluation Tool)")
        print("  • Lighthouse (Chrome DevTools)")
        print("  • Screen reader (NVDA gratis, JAWS, VoiceOver)")
        print("=" * 70)

WCAGPrinciples.print_all()

Accessibility checklist:

class AccessibilityChecklist:
    """Quick checklist para cada feature"""

    @staticmethod
    def print_checklist():
        checklist = """
        ♿ ACCESSIBILITY CHECKLIST
        ========================

        Before shipping ANY feature, verify:

        ☐ 1. KEYBOARD NAVIGATION
           • Can you use Tab to navigate?
           • Can you activate with Enter/Space?
           • Is focus visible?
           • No keyboard traps?

        ☐ 2. SCREEN READER
           • Images have alt text?
           • Buttons have labels?
           • Form inputs have labels?
           • Heading hierarchy correct?

        ☐ 3. COLOR & CONTRAST
           • Text contrast ≥ 4.5:1?
           • Don't rely only on color to convey info?
           • Visible in high contrast mode?

        ☐ 4. FORMS
           • Labels associated with inputs?
           • Error messages descriptive?
           • Required fields marked?

        ☐ 5. DYNAMIC CONTENT
           • ARIA live regions for updates?
           • Focus management in modals?
           • Loading states announced?

        ☐ 6. MOBILE
           • Touch targets ≥ 44x44px?
           • Works with screen reader?
           • Zoom doesn't break layout?

        🧪 Test with:
        • Keyboard only (unplug mouse)
        • Screen reader (NVDA/VoiceOver)
        • Browser zoom at 200%
        • High contrast mode
        """

        print(checklist)

AccessibilityChecklist.print_checklist()