- 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>
12 KiB
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>
7. Links de Ayuda
<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>
10. Campos Relacionados (Link)
<!-- 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;
}
}