-
Notifications
You must be signed in to change notification settings - Fork 0
Guía de estilo de programación
Agustín Figueroa Sierra edited this page Sep 6, 2025
·
1 revision
- Legibilidad sobre concisión: El código debe ser fácil de leer y entender
- Consistencia: Mantener el mismo estilo en todo el proyecto
- Documentación: Código autodocumentado con comentarios cuando sea necesario
- Mantenibilidad: Estructura que facilite futuras modificaciones
# ✅ CORRECTO
class Product(models.Model):
name = models.CharField(max_length=255, verbose_name='Nombre')
def get_user_recommendations(user):
"""Obtiene recomendaciones personalizadas para un usuario"""
pass
# ❌ INCORRECTO
class prod(models.Model): # Abreviación
n = models.CharField(max_length=255) # Nombre no descriptivo
def get_rec(u): # Abreviación y parámetro no descriptivo
passapp_name/
├── models.py # Modelos de datos
├── views.py # Vistas y lógica de presentación
├── forms.py # Formularios Django
├── admin.py # Configuración del admin
├── urls.py # URLs de la aplicación
├── services.py # Lógica de negocio (si existe)
├── utils.py # Utilidades y helpers
└── templates/ # Templates HTML
└── app_name/
# ✅ CORRECTO - Estructura clara y documentada
class Product(models.Model):
"""Modelo que representa un producto en el catálogo"""
CATEGORY_CHOICES = [
('Comida', 'Comida'),
('Ropa', 'Ropa'),
('Tecnología', 'Tecnología'),
]
name = models.CharField(
max_length=255,
verbose_name='Nombre',
help_text='Nombre del producto'
)
def clean(self):
"""Validación personalizada del modelo"""
if self.category == 'Comida':
self.condition = None
super().clean()
def __str__(self):
return f"{self.name} - {self.get_category_display()}"
class Meta:
verbose_name = 'Producto'
verbose_name_plural = 'Productos'
ordering = ['-published_at']# ✅ CORRECTO - Vista limpia y bien estructurada
@login_required
def add_product(request):
"""Vista para agregar un nuevo producto"""
if request.method == 'POST':
form = ProductForm(request.POST, request.FILES)
if form.is_valid():
product = form.save(commit=False)
product.seller = request.user
product.save()
messages.success(request, '¡Producto agregado exitosamente!')
return redirect('home')
else:
messages.error(request, 'Por favor corrige los errores en el formulario.')
else:
form = ProductForm()
return render(request, 'products/add_product.html', {'form': form})
# ❌ INCORRECTO - Vista "gorda" con mucha lógica
def add_product(request):
# 200+ líneas de lógica de negocio mezclada
pass# ✅ CORRECTO - Formulario bien estructurado
class ProductForm(forms.ModelForm):
class Meta:
model = Product
fields = ['name', 'category', 'description', 'price', 'image']
widgets = {
'name': forms.TextInput(attrs={'class': 'form-control'}),
'description': forms.Textarea(attrs={
'class': 'form-control',
'rows': 4,
'placeholder': 'Describe tu producto detalladamente'
}),
}
def clean(self):
"""Validación personalizada del formulario"""
cleaned_data = super().clean()
category = cleaned_data.get('category')
if category == 'Comida':
food_type = cleaned_data.get('food_type')
if not food_type:
self.add_error('food_type', 'Debe seleccionar un tipo de comida')
return cleaned_data# ✅ CORRECTO - Manejo explícito de errores
try:
processor = GeminiProcessor()
result = processor.process_query(query)
if result.get('success', False):
# Procesar resultado exitoso
pass
else:
messages.error(request, f"Error: {result.get('error', 'Error desconocido')}")
except Exception as e:
logger.error(f"Error procesando consulta: {str(e)}")
messages.error(request, 'Error interno del servidor')
# ❌ INCORRECTO - Ignorar errores
processor = GeminiProcessor()
result = processor.process_query(query) # Sin manejo de errores<!-- ✅ CORRECTO - Extensión del template base -->
{% extends 'products/base.html' %}
{% load humanize %}
{% block title %}ComercIA - Inicio{% endblock %}
{% block extra_styles %}
<style>
.product-card {
height: 100%;
transition: all 0.2s;
border: none;
box-shadow: 0 1px 3px rgba(0,0,0,0.08);
}
</style>
{% endblock %}
{% block content %}
<div class="container py-4">
<!-- Contenido principal -->
</div>
{% endblock %}<!-- ✅ CORRECTO - Uso apropiado de filtros -->
<h5 class="card-title">{{ product.name|truncatechars:50 }}</h5>
<p class="price">$ {{ product.price|floatformat:0|intcomma }}</p>
<span class="badge bg-primary">{{ product.get_category_display }}</span>
<!-- ❌ INCORRECTO - Lógica compleja en template -->
{% if product.name|length > 50 %}
{{ product.name|slice:":50" }}...
{% else %}
{{ product.name }}
{% endif %}<!-- ✅ CORRECTO - Formulario bien estructurado -->
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
<div class="mb-3">
<label for="{{ form.name.id_for_label }}" class="form-label">
{{ form.name.label }}
</label>
{{ form.name }}
{% if form.name.errors %}
<div class="text-danger">
{% for error in form.name.errors %}
<small>{{ error }}</small>
{% endfor %}
</div>
{% endif %}
</div>
<button type="submit" class="btn btn-primary">Guardar</button>
</form>// ✅ CORRECTO - Función bien documentada
function toggleFavorite(event, productId) {
/**
* Alterna el estado de favorito de un producto
* @param {Event} event - Evento del click
* @param {number} productId - ID del producto
*/
if (event) event.preventDefault();
fetch(`/favorite/toggle/${productId}/`, {
method: 'POST',
headers: {
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value,
'Content-Type': 'application/json'
}
})
.then(response => response.json())
.then(data => {
updateFavoriteButton(productId, data.status);
})
.catch(error => {
console.error('Error:', error);
showErrorMessage('Error al actualizar favoritos');
});
}
// ❌ INCORRECTO - Función sin documentar y mal nombrada
function toggle(e, id) {
// Sin documentación y nombres no descriptivos
}// ✅ CORRECTO - Delegación de eventos
document.addEventListener('DOMContentLoaded', function() {
// Delegar clicks de botones de favoritos
document.addEventListener('click', function(e) {
const btn = e.target.closest('.favorite-toggle');
if (!btn) return;
const productId = btn.getAttribute('data-product-id');
if (productId) {
toggleFavorite(e, productId);
}
});
});/* ✅ CORRECTO - Uso de variables CSS y nomenclatura BEM */
:root {
--primary-color: hwb(216 4% 67%);
--secondary-color: #6c757d;
--card-shadow: 0 10px 20px rgba(0,0,0,0.05);
--border-radius: 16px;
--transition-speed: 0.3s;
}
.product-card {
height: 100%;
transition: all var(--transition-speed);
border: none;
box-shadow: var(--card-shadow);
}
.product-card:hover {
transform: translateY(-3px);
box-shadow: var(--hover-shadow);
}
.product-card__image {
height: 160px;
object-fit: cover;
}
.product-card__title {
font-size: 0.9rem;
font-weight: 500;
line-height: 1.2;
}
/* ❌ INCORRECTO - Nombres no descriptivos y sin organización */
.card1 {
height: 100%;
}
.img {
height: 160px;
}# ✅ CORRECTO - Configuración clara y documentada
# X API v2 configuration
X_BEARER_TOKEN = "AAAAAAAAAAAAAAAAAAAAA..." # Obtener de https://developer.twitter.com
X_USER_ID = "1963212843915923456" # ID del usuario a analizar
X_USERNAME = "testingAPIs01" # Username sin @
X_MAX_RESULTS = 3 # Limitar cantidad de tweets por petición
# ❌ INCORRECTO - Configuración sin documentar
X_BEARER_TOKEN = "AAAAAAAAAAAAAAAAAAAAA..."
X_USER_ID = "1963212843915923456"# ✅ CORRECTO - Docstring completo
def recommend_categories_from_text(text: str, keyword_map: dict | None = None) -> list[str]:
"""
Recomienda categorías de productos basadas en el texto de entrada.
Args:
text (str): Texto a analizar para detectar categorías
keyword_map (dict, optional): Mapeo personalizado de palabras clave a categorías.
Si no se proporciona, usa el mapeo por defecto.
Returns:
list[str]: Lista de categorías detectadas, ordenadas alfabéticamente
Example:
>>> recommend_categories_from_text("Me gusta la comida como el mango")
['Comida']
"""
if not text:
return []
mapping = keyword_map or DEFAULT_KEYWORD_MAP
lowered = text.lower()
categories: set[str] = set()
for keyword, category in mapping.items():
if keyword in lowered:
categories.add(category)
return sorted(categories)# ✅ CORRECTO - Comentarios útiles y concisos
# Configuración para la zona horaria de Colombia (UTC-5)
COLOMBIA_TIMEZONE = pytz.timezone('America/Bogota')
# Si es un nuevo comentario, asignar la fecha actual con la zona horaria de Colombia
if not self.pk:
now_utc = timezone.now()
self.created_at = now_utc.astimezone(COLOMBIA_TIMEZONE)
# ❌ INCORRECTO - Comentarios obvios
# Asignar valor a variable
x = 5
# Incrementar contador
counter += 1# ✅ CORRECTO - Mensajes descriptivos y estructurados
git commit -m "feat: agregar búsqueda inteligente con Gemini AI
- Implementar GeminiProcessor para procesamiento de lenguaje natural
- Agregar endpoint /chat/search/ para consultas AJAX
- Crear modelo ChatQuery para historial de consultas
- Actualizar template home.html con interfaz de chatbot"
git commit -m "fix: corregir validación de formulario de productos
- Arreglar validación de food_type cuando category es 'Comida'
- Mejorar mensajes de error en ProductForm.clean()"
git commit -m "docs: actualizar guía de estilo de programación
- Agregar ejemplos de código para Python/Django
- Incluir convenciones para JavaScript y CSS
- Documentar patrones de manejo de errores"
# ❌ INCORRECTO - Mensajes vagos
git commit -m "fix"
git commit -m "update"
git commit -m "changes"# ✅ CORRECTO - Nomenclatura clara de branches
feature/gemini-ai-integration
feature/telegram-support
bugfix/product-form-validation
hotfix/security-patch
docs/style-guide-update
# ❌ INCORRECTO - Nombres confusos
branch1
test
fix
new-feature# ✅ CORRECTO - Test bien estructurado
class ProductModelTest(TestCase):
"""Tests para el modelo Product"""
def setUp(self):
"""Configuración inicial para cada test"""
self.user = User.objects.create_user(
username='testuser',
email='test@example.com',
password='testpass123'
)
def test_product_creation(self):
"""Test que verifica la creación correcta de un producto"""
product = Product.objects.create(
name='Producto de prueba',
category='Comida',
food_type='Snacks',
description='Descripción del producto',
price=1000.00,
seller=self.user
)
self.assertEqual(product.name, 'Producto de prueba')
self.assertEqual(product.category, 'Comida')
self.assertTrue(product.available)
def test_product_clean_method(self):
"""Test que verifica la validación del método clean"""
product = Product(
name='Producto de prueba',
category='Comida',
condition='Nuevo', # No debería aplicarse para comida
description='Descripción',
price=1000.00,
seller=self.user
)
product.clean()
self.assertIsNone(product.condition)# ✅ CORRECTO - Configuración por entornos
DEBUG = 'RENDER' not in os.environ
# Permitir conexiones desde otros dispositivos en la red local y ngrok
ALLOWED_HOSTS = []
RENDER_EXTERNAL_HOSTNAME = os.environ.get('RENDER_EXTERNAL_HOSTNAME')
if RENDER_EXTERNAL_HOSTNAME:
ALLOWED_HOSTS.append(RENDER_EXTERNAL_HOSTNAME)
# Configuración CSRF para ngrok
CSRF_TRUSTED_ORIGINS = [
'https://*.ngrok-free.app',
'https://*.ngrok.io',
'https://*.ngrok.app',
]- El código sigue las convenciones de nomenclatura
- Las funciones tienen docstrings apropiados
- Se manejan los errores correctamente
- Los templates extienden el base.html
- Los formularios tienen validación apropiada
- El CSS usa variables y nomenclatura consistente
- Los mensajes de commit son descriptivos
- No hay código comentado innecesario
- Las importaciones están organizadas
- El código ha sido probado localmente
- Se han ejecutado los tests existentes
- La documentación está actualizada
- El PR tiene una descripción clara
- Se han revisado los cambios de migración
- No hay conflictos con la rama principal
# Instalar herramientas de calidad de código
pip install flake8 black isort
# Formatear código automáticamente
black .
isort .
# Verificar estilo de código
flake8 .# .pre-commit-config.yaml
repos:
- repo: https://github.com/psf/black
rev: 22.3.0
hooks:
- id: black
- repo: https://github.com/pycqa/isort
rev: 5.10.1
hooks:
- id: isort
- repo: https://github.com/pycqa/flake8
rev: 4.0.1
hooks:
- id: flake8comercIA/
├── products/ # App principal de productos
├── seller_profiles/ # App de perfiles de vendedores
├── social_ingestion/ # App de ingesta de redes sociales
├── comercia/ # Configuración del proyecto
├── media/ # Archivos multimedia
├── staticfiles/ # Archivos estáticos
└── docs/ # Documentación
# ✅ CORRECTO - Procesador de IA bien estructurado
class GeminiProcessor:
def __init__(self):
self.api_key = os.getenv('GEMINI_API_KEY')
self.model_name = "gemini-1.5-flash"
self.api_url = f"https://generativelanguage.googleapis.com/v1beta/models/{self.model_name}:generateContent"
def process_query(self, query):
"""Procesa consulta en lenguaje natural y extrae palabras clave"""
try:
# Lógica de procesamiento
pass
except Exception as e:
return {"success": False, "error": str(e)}# ✅ CORRECTO - Manejo de APIs con rate limiting
def _fetch_x_for_account(self, account: SocialAccount) -> list[dict[str, Any]]:
bearer_token = getattr(settings, "X_BEARER_TOKEN", "").strip()
if not bearer_token or bearer_token == "TU_TOKEN_DE_ACCESO_AQUI":
self.stdout.write(self.style.WARNING("X_BEARER_TOKEN no configurado."))
return []
# Lógica de fetch con manejo de rate limits
backoff_seconds = 5
for attempt in range(3):
try:
response = requests.get(url, headers=headers, params=params, timeout=15)
if response.status_code == 200:
return self._parse_response(response)
elif response.status_code == 429:
self.stdout.write(self.style.WARNING(f"Rate limit X (429). Reintentando en {backoff_seconds}s..."))
time.sleep(backoff_seconds)
backoff_seconds *= 2
continue
except requests.exceptions.RequestException as e:
self.stdout.write(self.style.ERROR(f"X API request failed: {e}"))
break
return []Esta guía debe ser seguida por todos los desarrolladores del proyecto ComercIA para mantener la consistencia y calidad del código.