Ir al contenido

Code Style Conventions

Convenciones de Estilo de Codigo — Guia Normativa

Sección titulada «Convenciones de Estilo de Codigo — Guia Normativa»

AI Software Factory OS v7.7 — Guia Normativa

Sección titulada «AI Software Factory OS v7.7 — Guia Normativa»

Version: 1.0.0 | Fecha: Marzo 2026 Aplica a: Todo codigo generado por humanos y por agentes AI Enforcement: compliance-linter.py, reglas en .claude/rules/code_quality.md


  1. Legibilidad sobre cleverness — El codigo se lee 10x mas de lo que se escribe. Preferir claridad sobre concision ingeniosa.
  2. Consistencia dentro del proyecto — Una convencion mediocre aplicada consistentemente es mejor que dos buenas aplicadas parcialmente.
  3. Codigo AI = mismo estandar — El codigo generado por agentes DEBE cumplir las mismas reglas. No hay excepcion por origen.
  4. MUST vs SHOULD — Las reglas marcadas MUST son obligatorias y bloquean CI. Las marcadas SHOULD son recomendadas y generan warnings.
NivelSignificadoEfecto en CI
MUSTObligatorioBloquea merge si no se cumple
SHOULDRecomendadoWarning en linter, no bloquea
MAYOpcionalInformativo

ElementoConvencionEjemplo
Variables / funcionescamelCasegetUserById, isActive
Clases / interfaces / typesPascalCaseUserService, ButtonProps
ConstantesUPPER_SNAKE_CASEMAX_RETRIES, API_BASE_URL
Archivos de modulokebab-case.tsuser-service.ts, auth-middleware.ts
Componentes ReactPascalCase.tsxUserProfile.tsx, DataTable.tsx
HooksuseNombreHookuseAuth.ts, usePagination.ts
EnumsPascalCase, miembros UPPER_SNAKE_CASEenum Status { ACTIVE, INACTIVE }

CORRECTO:

user-repository.ts
const MAX_PAGE_SIZE = 100;
interface UserFilterOptions {
status: UserStatus;
createdAfter?: Date;
}
function findUsersByFilter(options: UserFilterOptions): Promise<User[]> {
const { status, createdAfter } = options;
// ...
}

INCORRECTO:

// Archivo: UserRepository.ts ← nombre de archivo PascalCase
const max_page_size = 100; // ← constante en snake_case
interface userfilter { // ← interface en minusculas, sin sufijo descriptivo
Status: string; // ← propiedad PascalCase, tipo string generico
}
function Find_Users(s: any, d: any): any { // ← naming mixto, parametros any
// ...
}
ReglaNivel
Max 30 lineas por funcionSHOULD
Max 50 lineas por funcionMUST
Max 4 parametrosSHOULD (usar objeto config si mas)
Tipo de retorno explicito en funciones publicasMUST
JSDoc en funciones exportadasSHOULD
Arrow functions para callbacksSHOULD
Function declarations para exportsSHOULD

CORRECTO:

/** Calcula el precio total con impuestos y descuentos aplicados. */
export function calculateTotalPrice(items: CartItem[], taxRate: number): Money {
const subtotal = items.reduce((sum, item) => sum + item.price * item.quantity, 0);
const tax = subtotal * taxRate;
return { amount: subtotal + tax, currency: "USD" };
}
// Callback con arrow function
const activeUsers = users.filter((user) => user.isActive);

INCORRECTO:

// Sin tipo de retorno, sin documentacion, demasiados parametros sueltos
export function calc(items, tax, discount, shipping, coupon, giftCard, membership) {
// 60 lineas de logica mezclada...
// ...imposible de entender sin leer todo
}
// Function declaration para callback inline
const activeUsers = users.filter(function filterActive(user) {
return user.isActive;
});
ReglaNivel
Max 300 lineas por archivoSHOULD
Max 500 lineas por archivoMUST
1 export principal por archivoSHOULD
Imports agrupados y ordenadosMUST

Orden de imports:

// 1. Externos (node_modules)
import { useState, useEffect } from "react";
import { z } from "zod";
// 2. Internos (paths del proyecto)
import { UserService } from "@/services/user-service";
import { formatDate } from "@/utils/date-helpers";
// 3. Tipos
import type { User, UserRole } from "@/types/user";
// 4. Estilos
import styles from "./user-profile.module.css";
ReglaNivel
Function components (nunca class)MUST
Props con interface + sufijo PropsMUST
Hooks custom en archivo separadoSHOULD
Memoization solo con medicion previaSHOULD

CORRECTO:

interface UserCardProps {
user: User;
onSelect: (userId: string) => void;
}
export function UserCard({ user, onSelect }: UserCardProps): JSX.Element {
const handleClick = () => onSelect(user.id);
return (
<div className="user-card" onClick={handleClick}>
<h3>{user.name}</h3>
<span>{user.email}</span>
</div>
);
}

INCORRECTO:

// Class component, props sin tipar, logica mezclada
class UserCard extends React.Component {
render() {
return (
<div onClick={() => this.props.onSelect(this.props.user.id)}>
<h3>{this.props.user.name}</h3>
</div>
);
}
}
ReglaNivel
Preferir interface sobre type (excepto unions)SHOULD
No usar anyMUST
readonly para immutabilitySHOULD
Enums sobre string literals cuando hay 4+ opcionesSHOULD
  • Custom error classes que extiendan Error con name descriptivo.
  • try/catch con tipos especificos — nunca catch generico sin manejo.
  • Logging estructurado en produccion — nunca console.log suelto.
  • Tests: describe/it/expect, patron Arrange/Act/Assert.
  • Naming de tests: "should [comportamiento] when [condicion]".

ElementoConvencionEjemplo
Variables / funcionessnake_caseget_user_by_id, is_valid
ClasesPascalCaseUserService, HttpClient
ConstantesUPPER_SNAKE_CASEMAX_RETRIES, DEFAULT_TIMEOUT
Archivos / modulossnake_case.pyuser_service.py, db_utils.py
Protected_prefijo_internal_cache
Private__prefijo (rara vez)__secret_key
ReglaNivel
Type hints en parametros y retornoMUST
Docstrings Google style en funciones publicasMUST
Max 30 lineas por funcionSHOULD
Max 50 lineas por funcionMUST
Max 4 parametros (usar dataclass si mas)SHOULD
if __name__ == "__main__": en scriptsMUST

CORRECTO:

from dataclasses import dataclass
from datetime import datetime
@dataclass
class UserFilter:
"""Filtros para busqueda de usuarios."""
status: str
created_after: datetime | None = None
role: str | None = None
def find_users_by_filter(
db: Database,
filters: UserFilter,
) -> list[User]:
"""Busca usuarios que coincidan con los filtros dados.
Args:
db: Conexion activa a la base de datos.
filters: Criterios de busqueda.
Returns:
Lista de usuarios que cumplen los filtros.
Raises:
DatabaseError: Si la conexion falla.
"""
query = db.query(User).filter(User.status == filters.status)
if filters.created_after:
query = query.filter(User.created_at >= filters.created_after)
return query.all()

INCORRECTO:

# Sin type hints, sin docstring, parametros sueltos, nombre no descriptivo
def find(db, s, d=None, r=None, limit=10, offset=0, sort="asc"):
# 80 lineas de logica sin estructura...
results = db.query("SELECT * FROM users WHERE status = '" + s + "'")
print(f"Found {len(results)} users") # print en vez de logging
return results
ReglaNivel
Max 500 lineas por archivoSHOULD
Imports agrupados: stdlib, third-party, localMUST
No import *MUST

CORRECTO:

# 1. Standard library
import logging
from pathlib import Path
# 2. Third-party
import yaml
from pydantic import BaseModel
# 3. Local
from .models import User
from .exceptions import ValidationError

INCORRECTO:

from os import * # import wildcard
import yaml
from .models import User
import logging # stdlib mezclado con third-party
from pydantic import BaseModel
  • Custom exceptions que hereden de Exception con nombres descriptivos.
  • Modulo logging — nunca print en produccion.
  • try/except con excepciones especificas — nunca except Exception sin re-raise.
  • Tests con pytest (no unittest en proyectos nuevos).
  • Naming: test_should_[comportamiento]_when_[condicion].
  • Fixtures con conftest.py.

CORRECTO:

class InsufficientFundsError(Exception):
"""El usuario no tiene saldo suficiente para la operacion."""
def withdraw(account: Account, amount: Decimal) -> Transaction:
if account.balance < amount:
raise InsufficientFundsError(
f"Balance {account.balance} insuficiente para retiro de {amount}"
)
return account.create_transaction(-amount)

INCORRECTO:

def withdraw(account, amount):
try:
if account.balance < amount:
raise Exception("error") # mensaje generico, clase generica
return account.create_transaction(-amount)
except: # bare except — atrapa TODO incluyendo KeyboardInterrupt
print("algo fallo")
return None # retorna None silenciosamente

ReglaNivel
set -euo pipefail despues del shebangMUST
Shebang: #!/usr/bin/env bashMUST
Variables constantes: UPPER_SNAKE_CASEMUST
Variables locales: lower_snake_case con localSHOULD
Funciones: snake_caseMUST
Archivos: kebab-case.shSHOULD
Colores definidos como constantes al inicioSHOULD

Todo script DEBE seguir este orden:

#!/usr/bin/env bash
set -euo pipefail
# ─── Constantes y colores ───
readonly RED='\033[0;31m'
readonly GREEN='\033[0;32m'
readonly YELLOW='\033[1;33m'
readonly NC='\033[0m'
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# ─── Funciones auxiliares ───
log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1" >&2; }
log_error() { echo -e "${RED}[ERROR]${NC} $1" >&2; }
cleanup() {
# Limpieza de archivos temporales
rm -f "${tmp_file:-}"
}
trap cleanup EXIT
validate_input() {
local input="$1"
[[ -z "$input" ]] && { log_error "Input requerido"; exit 2; }
}
# ─── Main ───
main() {
local target="${1:-}"
validate_input "$target"
log_info "Procesando: $target"
# ...logica principal
}
main "$@"

INCORRECTO:

Ventana de terminal
# Sin shebang, sin set -euo pipefail, sin estructura
echo "starting..."
TARGET=$1
if [ "$TARGET" = "" ]; then
echo "need target" # no va a stderr, sin colores, sin exit code
fi
# logica mezclada sin funciones, variables globales sin readonly
result=$(curl $TARGET) # variable sin comillas
echo $result
Exit codeSignificado
0Exito
1Warning / error recuperable
2Error fatal / input invalido
  • Mensajes de error SIEMPRE a stderr (>&2).
  • trap para cleanup de archivos temporales.
  • Comillas dobles en TODAS las expansiones de variables: "$var", no $var.

ElementoConvencionEjemplo
Tablasplural, snake_caseusers, ticket_events
Columnassnake_casecreated_at, user_id
Foreign keystabla_singular_iduser_id, order_id
Indicesidx_tabla_columnaidx_users_email
Primary keyspk_tablapk_users
Unique constraintsuq_tabla_columnauq_users_email
Foreign key constraintsfk_tabla_referenciafk_orders_user_id
ReglaNivel
Keywords SQL en UPPERCASEMUST
1 columna por linea en CREATE TABLESHOULD
Timestamps: siempre TIMESTAMPTZMUST
created_at + updated_at en tablas mutablesMUST
UUID sobre SERIAL para sistemas distribuidosSHOULD

CORRECTO:

CREATE TABLE orders (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id),
status VARCHAR(20) NOT NULL DEFAULT 'pending',
total_cents INTEGER NOT NULL CHECK (total_cents >= 0),
notes TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX idx_orders_user_id ON orders(user_id);
CREATE INDEX idx_orders_status ON orders(status);
SELECT
o.id,
o.status,
u.email
FROM orders o
JOIN users u ON u.id = o.user_id
WHERE o.status = 'pending'
AND o.created_at >= now() - INTERVAL '7 days'
ORDER BY o.created_at DESC;

INCORRECTO:

-- Tabla singular, sin timestamps, TIMESTAMP sin timezone, naming inconsistente
create table order (
ID serial primary key,
userId int references users(ID), -- camelCase en columna
Status varchar(20), -- PascalCase
total float, -- float para dinero = errores de precision
created timestamp -- TIMESTAMP sin TZ, sin default, sin updated_at
);
select * from order where Status = 'pending'; -- SELECT * en produccion, keywords lowercase
ReglaNivel
Archivo: NNN_description.sql (up)MUST
Rollback: NNN_description.down.sqlMUST
Siempre reversiblesMUST
Zero-downtime: aditivo primeroSHOULD

Estrategia zero-downtime para cambios destructivos:

  1. Migracion 1: Agregar columna nueva (aditivo).
  2. Deploy: Codigo escribe a ambas columnas.
  3. Migracion 2: Backfill datos.
  4. Deploy: Codigo lee de columna nueva.
  5. Migracion 3: Eliminar columna vieja.

ReglaNivel
Indentacion: 2 espaciosMUST
Nunca tabsMUST
Strings sin comillas (excepto caracteres especiales)SHOULD
Comentarios en espanol (framework)MUST
Separadores de seccion: # --- Seccion ---SHOULD

CORRECTO:

# --- Configuracion del proyecto ---
project:
name: mi-proyecto
version: 1.0.0
# Activar monitoreo para ambientes de produccion
monitoring:
enabled: true
interval_seconds: 30
endpoints:
- /health
- /metrics

INCORRECTO:

# tabs, comillas innecesarias, sin estructura
project:
name: "mi-proyecto" # 4 espacios (debe ser 2)
version: "1.0.0"
monitoring:
enabled: "true" # string en vez de boolean
interval_seconds: "30" # string en vez de integer
ReglaNivel
Version 3.1.0SHOULD
operationId en cada endpointMUST
Schemas reutilizables en components/schemas/SHOULD
Ejemplos en cada schemaSHOULD

ReglaNivel
Max 50 lineas por funcionMUST
Max 500 lineas por archivoMUST
No anyMUST
Tipo de retorno explicito en exportsMUST
Interface con sufijo Props en ReactMUST
Function components (no class)MUST
Imports agrupados y ordenadosMUST
Max 30 lineas por funcionSHOULD
Max 300 lineas por archivoSHOULD
JSDoc en funciones exportadasSHOULD
readonly para immutabilitySHOULD
1 export principal por archivoSHOULD
ReglaNivel
Type hints en parametros y retornoMUST
Docstrings Google style en funciones publicasMUST
Max 50 lineas por funcionMUST
if __name__ == "__main__": en scriptsMUST
No import *MUST
Imports agrupadosMUST
Max 30 lineas por funcionSHOULD
Max 500 lineas por archivoSHOULD
pytest sobre unittestSHOULD
ReglaNivel
set -euo pipefailMUST
#!/usr/bin/env bashMUST
Constantes UPPER_SNAKE_CASEMUST
Funciones snake_caseMUST
Errores a stderrMUST
Variables locales con localSHOULD
Colores como constantesSHOULD
ReglaNivel
Keywords UPPERCASEMUST
TIMESTAMPTZ (no TIMESTAMP)MUST
created_at + updated_at en tablas mutablesMUST
Migraciones con rollbackMUST
1 columna por linea en CREATE TABLESHOULD
UUID sobre SERIAL en distribuidosSHOULD
ReglaNivel
Indentacion 2 espaciosMUST
Nunca tabsMUST
Comentarios en espanol (framework)MUST
Strings sin comillas innecesariasSHOULD

LenguajeLinterFormatterConfig
TypeScriptESLintPrettier.eslintrc.json + .prettierrc
Pythonruffruff formatpyproject.toml seccion [tool.ruff]
Bashshellcheckshfmt.shellcheckrc
SQLsqlfluffsqlfluff fix.sqlfluff
YAMLyamllintyamllint.yamllint.yaml

ESLint (TypeScript):

{
"rules": {
"@typescript-eslint/no-explicit-any": "error",
"@typescript-eslint/explicit-function-return-type": "warn",
"max-lines-per-function": ["warn", { "max": 50 }],
"max-lines": ["warn", { "max": 500 }],
"max-params": ["warn", { "max": 4 }]
}
}

ruff (Python):

[tool.ruff]
line-length = 100
target-version = "py312"
[tool.ruff.lint]
select = ["E", "F", "I", "N", "UP", "ANN", "S", "B"]
# E=pycodestyle, F=pyflakes, I=isort, N=pep8-naming
# UP=pyupgrade, ANN=annotations, S=bandit, B=bugbear

shellcheck:

Ventana de terminal
# En CI:
shellcheck -e SC1091 scripts/*.sh

Todos los linters DEBEN correr en el pipeline de CI. Las reglas MUST bloquean merge; las reglas SHOULD generan warnings visibles en el PR.

# Ejemplo GitHub Actions
- name: Lint TypeScript
run: npx eslint . --max-warnings=0
- name: Lint Python
run: ruff check . && ruff format --check .
- name: Lint Bash
run: shellcheck scripts/*.sh
- name: Lint YAML
run: yamllint -s .

Los agentes autonomos (FABs) DEBEN:

  1. Generar codigo que cumpla estas convenciones sin necesidad de correccion humana.
  2. Ejecutar linters antes de commit — el pre-commit hook valida automaticamente.
  3. No introducir any, print(), console.log() en codigo de produccion.
  4. Respetar limites de lineas — si una funcion crece mas de 30 lineas, refactorizar.
  5. Incluir type hints y docstrings en toda funcion publica desde la primera iteracion.

El compliance-linter.py verifica estas convenciones automaticamente en gates B (Architecture) y C (Implementation).