Files
roi-theme/_planificacion/01-design-system/08-PATRONES-FORMULARIOS.md
FrankZamora 0f6387ab46 refactor: reorganizar openspec y planificacion con spec recaptcha
- renombrar openspec/ a _openspec/ (carpeta auxiliar)
- mover specs de features a changes/
- crear specs base: arquitectura-limpia, estandares-codigo, nomenclatura
- migrar _planificacion/ con design-system y roi-theme-template
- agregar especificacion recaptcha anti-spam (proposal, tasks, spec)
- corregir rutas y referencias en todas las specs

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-08 15:30:45 -06:00

12 KiB

📝 PATRONES DE FORMULARIOS

1. Form Switch (Checkbox Toggle)

<div class="mb-2">
    <div class="form-check form-switch">
        <input class="form-check-input" type="checkbox" id="enabled" checked>
        <label class="form-check-label small" for="enabled" style="color: #495057;">
            <i class="bi bi-toggle-on me-1" style="color: #FF8600;"></i>
            <strong>Activar Top Bar</strong>
            <span class="text-muted">(Mostrar en el sitio)</span>
        </label>
    </div>
</div>

Uso: Activación/desactivación de features

CSS necesario (Fix WordPress):

/* Eliminar pseudo-elementos de WordPress */
body .form-switch .form-check-input[type="checkbox"]::before,
body .form-switch .form-check-input[type="checkbox"]::after {
    content: none !important;
    display: none !important;
}

body .form-switch .form-check-input[type="checkbox"] {
    background-size: contain !important;
    background-repeat: no-repeat !important;
    background-position: left center !important;
}

body .form-switch .form-check-input[type="checkbox"]:checked {
    background-position: right center !important;
}

/* Fix alineación vertical */
.form-check.form-switch {
    display: flex !important;
    align-items: center !important;
}

.form-switch .form-check-input {
    margin-top: 0 !important;
    margin-bottom: 0 !important;
}

.form-switch .form-check-label {
    line-height: 16px !important;
    padding-top: 0 !important;
    margin-top: 0 !important;
}

2. Color Picker

<div class="col-6">
    <label for="bgColor" class="form-label small mb-1 fw-semibold" style="color: #495057;">
        <i class="bi bi-paint-bucket me-1" style="color: #FF8600;"></i>
        Color de Fondo
    </label>
    <input type="color" id="bgColor"
           class="form-control form-control-color w-100"
           value="#0E2337"
           title="Seleccionar color de fondo">
    <small class="text-muted d-block mt-1" id="bgColorValue">#0E2337</small>
</div>

Características:

  • Grid de 2 columnas (col-6)
  • Display del valor hex debajo
  • Width 100% (.w-100)

JavaScript:

const colorInput = document.getElementById('bgColor');
const colorValue = document.getElementById('bgColorValue');

colorInput.addEventListener('input', function() {
    colorValue.textContent = this.value.toUpperCase();
    updatePreview();
});

3. Text Input con Icono

<div class="mb-2">
    <label for="highlightText" class="form-label small mb-1 fw-semibold" style="color: #495057;">
        <i class="bi bi-chat-text me-1" style="color: #FF8600;"></i>
        Texto Destacado
        <span class="text-danger">*</span> <!-- Si es requerido -->
    </label>
    <input type="text" id="highlightText"
           class="form-control form-control-sm"
           placeholder="Ej: Nuevo:"
           value="Nuevo:"
           maxlength="50">
</div>

Uso: Campos de texto cortos (nombres, títulos, etc.)


4. Textarea con Contador y Progress Bar

<div class="mb-2">
    <label for="messageText" class="form-label small mb-1 fw-semibold" style="color: #495057;">
        <i class="bi bi-chat-left-text me-1" style="color: #FF8600;"></i>
        Mensaje Principal <span class="text-danger">*</span>
        <span class="float-end text-muted">
            <span id="messageTextCount" class="fw-bold">0</span>/250
        </span>
    </label>
    <textarea id="messageText"
              class="form-control form-control-sm"
              rows="2"
              maxlength="250"
              placeholder="Ej: Accede a más de 200,000 APUs actualizados"
              required></textarea>
    <div class="progress mt-1" style="height: 3px;">
        <div id="messageTextProgress"
             class="progress-bar"
             role="progressbar"
             style="width: 0%; background-color: #FF8600;"
             aria-valuenow="0"
             aria-valuemin="0"
             aria-valuemax="250"></div>
    </div>
</div>

JavaScript:

const textarea = document.getElementById('messageText');
const counter = document.getElementById('messageTextCount');
const progress = document.getElementById('messageTextProgress');

textarea.addEventListener('input', function() {
    const length = this.value.length;
    const maxLength = 250;
    const percentage = (length / maxLength) * 100;

    counter.textContent = length;
    progress.style.width = percentage + '%';
    progress.setAttribute('aria-valuenow', length);

    // Cambiar color según el uso
    if (percentage > 90) {
        progress.style.backgroundColor = '#dc3545'; // Rojo
    } else if (percentage > 75) {
        progress.style.backgroundColor = '#ffc107'; // Amarillo
    } else {
        progress.style.backgroundColor = '#FF8600'; // Orange
    }

    updatePreview();
});

5. Select Dropdown

<div class="mb-2">
    <label for="fontSize" class="form-label small mb-1 fw-semibold" style="color: #495057;">
        <i class="bi bi-fonts me-1" style="color: #FF8600;"></i>
        Tamaño de Fuente
    </label>
    <select id="fontSize" class="form-select form-select-sm">
        <option value="small">Pequeño (0.875rem)</option>
        <option value="normal" selected>Normal (1rem)</option>
        <option value="large">Grande (1.125rem)</option>
    </select>
</div>

Uso: Opciones predefinidas (tamaños, estilos, etc.)


6. Badges Informativos

Badge en Label

<label class="form-label small mb-1 fw-semibold">
    <i class="bi bi-code me-1" style="color: #FF8600;"></i>
    Clase del Icono
    <span class="badge bg-secondary" style="font-size: 0.65rem;">Bootstrap Icons</span>
</label>

Badge Standalone

<!-- Opcional -->
<span class="badge text-dark" style="background-color: #FFB800; font-size: 0.65rem;">
    Opcional
</span>

<!-- Info -->
<span class="badge bg-secondary" style="font-size: 0.65rem;">
    Info
</span>

<!-- Requerido -->
<span class="badge bg-danger" style="font-size: 0.65rem;">
    Requerido
</span>

<small class="text-muted d-block mt-1">
    <i class="bi bi-info-circle me-1"></i>
    Ver: <a href="https://icons.getbootstrap.com/" target="_blank"
            class="text-decoration-none" style="color: #FF8600;">
        Bootstrap Icons <i class="bi bi-box-arrow-up-right"></i>
    </a>
</small>

Características:

  • Icono de info
  • Link orange con hover
  • Icono de "abrir en nueva ventana"
  • target="_blank" para abrir en pestaña nueva

8. Grid de Inputs Compactos

2 Columnas Iguales

<div class="row g-2 mb-2">
    <div class="col-6">
        <label for="bgColor" class="form-label small mb-1 fw-semibold" style="color: #495057;">
            <i class="bi bi-paint-bucket me-1" style="color: #FF8600;"></i>
            Color Fondo
        </label>
        <input type="color" id="bgColor"
               class="form-control form-control-color w-100"
               value="#0E2337">
        <small class="text-muted d-block mt-1" id="bgColorValue">#0E2337</small>
    </div>
    <div class="col-6">
        <label for="textColor" class="form-label small mb-1 fw-semibold" style="color: #495057;">
            <i class="bi bi-fonts me-1" style="color: #FF8600;"></i>
            Color Texto
        </label>
        <input type="color" id="textColor"
               class="form-control form-control-color w-100"
               value="#ffffff">
        <small class="text-muted d-block mt-1" id="textColorValue">#FFFFFF</small>
    </div>
</div>

3 Columnas Desiguales

<div class="row g-2 mb-2">
    <div class="col-5">
        <label class="form-label small mb-1 fw-semibold">Campo 1</label>
        <input type="text" class="form-control form-control-sm">
    </div>
    <div class="col-5">
        <label class="form-label small mb-1 fw-semibold">Campo 2</label>
        <input type="text" class="form-control form-control-sm">
    </div>
    <div class="col-2">
        <label class="form-label small mb-1 fw-semibold">Icono</label>
        <input type="text" class="form-control form-control-sm">
    </div>
</div>

9. Campo de URL

<div class="mb-2">
    <label for="linkUrl" class="form-label small mb-1 fw-semibold" style="color: #495057;">
        <i class="bi bi-link-45deg me-1" style="color: #FF8600;"></i>
        URL del Enlace
    </label>
    <input type="url" id="linkUrl"
           class="form-control form-control-sm"
           placeholder="https://ejemplo.com"
           value="/catalogo">
    <small class="text-muted d-block mt-1">
        <i class="bi bi-info-circle me-1"></i>
        Usa rutas relativas (/) o absolutas (https://)
    </small>
</div>

<!-- Switch para mostrar/ocultar link -->
<div class="mb-2">
    <div class="form-check form-switch">
        <input class="form-check-input" type="checkbox" id="showLink" checked>
        <label class="form-check-label small" for="showLink" style="color: #495057;">
            <i class="bi bi-link me-1" style="color: #FF8600;"></i>
            <strong>Mostrar Enlace</strong>
            <span class="text-muted">(Call-to-action)</span>
        </label>
    </div>
</div>

<!-- Campos del link (se muestran/ocultan según el switch) -->
<div id="linkFields">
    <!-- Texto del link -->
    <div class="mb-2">
        <label for="linkText" class="form-label small mb-1 fw-semibold" style="color: #495057;">
            <i class="bi bi-chat-text me-1" style="color: #FF8600;"></i>
            Texto del Enlace
        </label>
        <input type="text" id="linkText"
               class="form-control form-control-sm"
               placeholder="Ej: Ver Catálogo →"
               value="Ver Catálogo →"
               maxlength="50">
    </div>

    <!-- URL del link -->
    <div class="mb-2">
        <label for="linkUrl" class="form-label small mb-1 fw-semibold" style="color: #495057;">
            <i class="bi bi-link-45deg me-1" style="color: #FF8600;"></i>
            URL del Enlace
        </label>
        <input type="url" id="linkUrl"
               class="form-control form-control-sm"
               placeholder="https://ejemplo.com"
               value="/catalogo">
    </div>

    <!-- Target del link -->
    <div class="mb-2">
        <label for="linkTarget" class="form-label small mb-1 fw-semibold" style="color: #495057;">
            <i class="bi bi-box-arrow-up-right me-1" style="color: #FF8600;"></i>
            Abrir En
        </label>
        <select id="linkTarget" class="form-select form-select-sm">
            <option value="_self" selected>Misma ventana (_self)</option>
            <option value="_blank">Nueva pestaña (_blank)</option>
        </select>
    </div>
</div>

JavaScript para mostrar/ocultar:

const showLink = document.getElementById('showLink');
const linkFields = document.getElementById('linkFields');

showLink.addEventListener('change', function() {
    linkFields.style.display = this.checked ? 'block' : 'none';
    updatePreview();
});

11. Validación de Campos

/**
 * Valida los campos del formulario
 */
function validateForm() {
    let isValid = true;
    const errors = [];

    // Validar campo requerido
    const messageText = document.getElementById('messageText').value.trim();
    if (!messageText) {
        errors.push('El mensaje principal es requerido');
        isValid = false;
    }

    // Validar longitud
    if (messageText.length > 250) {
        errors.push('El mensaje no puede exceder 250 caracteres');
        isValid = false;
    }

    // Validar URL
    const linkUrl = document.getElementById('linkUrl').value.trim();
    if (linkUrl && !isValidUrl(linkUrl)) {
        errors.push('La URL no es válida');
        isValid = false;
    }

    if (!isValid) {
        alert('⚠️ Errores de validación:\n\n' + errors.join('\n'));
    }

    return isValid;
}

function isValidUrl(string) {
    // Permitir rutas relativas
    if (string.startsWith('/')) {
        return true;
    }

    // Validar URLs absolutas
    try {
        new URL(string);
        return true;
    } catch (_) {
        return false;
    }
}

Volver al Índice

← Volver al README