API Validación DNI: Implementación en JavaScript y Python
Aprende a implementar APIs de validación de DNI español en JavaScript y Python. Código completo, mejores prácticas y consideraciones de seguridad.
Introducción a las APIs de Validación DNI
La validación de DNI es un proceso crítico en muchas aplicaciones web y móviles. Implementar una API robusta de validación no solo mejora la experiencia del usuario, sino que también garantiza la integridad de los datos y el cumplimiento de regulaciones.
Algoritmo de Validación DNI
Fundamentos Matemáticos
El DNI español utiliza un algoritmo de módulo 23 para calcular la letra de control:
const LETRAS_DNI = 'TRWAGMYFPDXBNJZSQVHLCKE';
function calcularLetraDNI(numero) {
const resto = numero % 23;
return LETRAS_DNI[resto];
}
Validación Completa
Un DNI válido debe cumplir:
- 8 dígitos numéricos
- 1 letra de control calculada correctamente
- Formato correcto (NNNNNNNNL)
Implementación en JavaScript
API REST con Express.js
const express = require('express');
const app = express();
// Middleware
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// Función de validación
function validarDNI(dni) {
const dniRegex = /^[0-9]{8}[TRWAGMYFPDXBNJZSQVHLCKE]$/i;
if (!dniRegex.test(dni)) {
return {
valido: false,
error: 'Formato inválido',
codigo: 'FORMATO_INVALIDO'
};
}
const numero = parseInt(dni.slice(0, 8));
const letra = dni.slice(8).toUpperCase();
const letraCalculada = calcularLetraDNI(numero);
return {
valido: letra === letraCalculada,
numero: numero,
letra: letra,
letraCalculada: letraCalculada,
error: letra !== letraCalculada ? 'Letra de control incorrecta' : null
};
}
// Endpoint de validación
app.post('/api/validar-dni', (req, res) => {
const { dni } = req.body;
if (!dni) {
return res.status(400).json({
error: 'DNI requerido',
codigo: 'DNI_REQUERIDO'
});
}
const resultado = validarDNI(dni);
res.json({
dni: dni,
timestamp: new Date().toISOString(),
resultado: resultado
});
});
// Endpoint GET para validación simple
app.get('/api/validar-dni/:dni', (req, res) => {
const { dni } = req.params;
const resultado = validarDNI(dni);
res.json({
dni: dni,
valido: resultado.valido,
timestamp: new Date().toISOString()
});
});
app.listen(3000, () => {
console.log('API de validación DNI ejecutándose en puerto 3000');
});
Validación Frontend
class DNIValidator {
constructor() {
this.letras = 'TRWAGMYFPDXBNJZSQVHLCKE';
this.regex = /^[0-9]{8}[TRWAGMYFPDXBNJZSQVHLCKE]$/i;
}
validar(dni) {
// Limpiar espacios y normalizar
dni = dni.replace(/\s/g, '').toUpperCase();
if (!this.regex.test(dni)) {
return {
valido: false,
error: 'Formato inválido'
};
}
const numero = parseInt(dni.slice(0, 8));
const letra = dni.slice(8);
const letraCalculada = this.letras[numero % 23];
return {
valido: letra === letraCalculada,
numero: numero,
letra: letra,
letraCalculada: letraCalculada
};
}
async validarRemoto(dni) {
try {
const response = await fetch('/api/validar-dni', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ dni })
});
return await response.json();
} catch (error) {
return {
error: 'Error de conexión',
codigo: 'CONEXION_ERROR'
};
}
}
}
Implementación en Python
API con Flask
from flask import Flask, request, jsonify
from datetime import datetime
import re
app = Flask(__name__)
class DNIValidator:
LETRAS = 'TRWAGMYFPDXBNJZSQVHLCKE'
PATRON = re.compile(r'^[0-9]{8}[TRWAGMYFPDXBNJZSQVHLCKE]$', re.IGNORECASE)
@classmethod
def calcular_letra(cls, numero):
"""Calcula la letra de control para un número de DNI"""
return cls.LETRAS[numero % 23]
@classmethod
def validar(cls, dni):
"""Valida un DNI completo"""
# Limpiar y normalizar
dni = dni.replace(' ', '').upper()
if not cls.PATRON.match(dni):
return {
'valido': False,
'error': 'Formato inválido',
'codigo': 'FORMATO_INVALIDO'
}
numero = int(dni[:8])
letra = dni[8]
letra_calculada = cls.calcular_letra(numero)
return {
'valido': letra == letra_calculada,
'numero': numero,
'letra': letra,
'letra_calculada': letra_calculada,
'error': None if letra == letra_calculada else 'Letra de control incorrecta'
}
@app.route('/api/validar-dni', methods=['POST'])
def validar_dni_post():
data = request.get_json()
if not data or 'dni' not in data:
return jsonify({
'error': 'DNI requerido',
'codigo': 'DNI_REQUERIDO'
}), 400
dni = data['dni']
resultado = DNIValidator.validar(dni)
return jsonify({
'dni': dni,
'timestamp': datetime.now().isoformat(),
'resultado': resultado
})
@app.route('/api/validar-dni/<dni>', methods=['GET'])
def validar_dni_get(dni):
resultado = DNIValidator.validar(dni)
return jsonify({
'dni': dni,
'valido': resultado['valido'],
'timestamp': datetime.now().isoformat()
})
@app.route('/api/generar-dni', methods=['GET'])
def generar_dni():
"""Genera un DNI válido aleatorio para testing"""
import random
numero = random.randint(10000000, 99999999)
letra = DNIValidator.calcular_letra(numero)
dni_generado = f"{numero}{letra}"
return jsonify({
'dni': dni_generado,
'numero': numero,
'letra': letra,
'timestamp': datetime.now().isoformat(),
'advertencia': 'Solo para testing - No es un DNI real'
})
if __name__ == '__main__':
app.run(debug=True)
Validación con FastAPI
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, validator
from datetime import datetime
from typing import Optional
app = FastAPI(title="API Validación DNI", version="1.0.0")
class DNIRequest(BaseModel):
dni: str
@validator('dni')
def validar_formato_basico(cls, v):
if not v or len(v.strip()) == 0:
raise ValueError('DNI no puede estar vacío')
return v.strip().upper()
class DNIResponse(BaseModel):
dni: str
valido: bool
numero: Optional[int] = None
letra: Optional[str] = None
letra_calculada: Optional[str] = None
error: Optional[str] = None
timestamp: datetime
@app.post("/validar-dni", response_model=DNIResponse)
async def validar_dni(request: DNIRequest):
resultado = DNIValidator.validar(request.dni)
return DNIResponse(
dni=request.dni,
valido=resultado['valido'],
numero=resultado.get('numero'),
letra=resultado.get('letra'),
letra_calculada=resultado.get('letra_calculada'),
error=resultado.get('error'),
timestamp=datetime.now()
)
@app.get("/healthcheck")
async def healthcheck():
return {"status": "ok", "timestamp": datetime.now()}
Consideraciones de Seguridad
Rate Limiting
const rateLimit = require('express-rate-limit');
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutos
max: 100, // límite de 100 requests por IP
message: 'Demasiadas solicitudes desde esta IP'
});
app.use('/api/', limiter);
Validación de Entrada
- Sanitización de datos de entrada
- Validación de longitud y formato
- Protección contra inyección de código
- Logging de intentos sospechosos
CORS y Headers de Seguridad
const cors = require('cors');
const helmet = require('helmet');
app.use(helmet());
app.use(cors({
origin: ['https://tudominio.com'],
methods: ['GET', 'POST'],
credentials: true
}));
Testing y Documentación
Tests Unitarios (Jest)
const { validarDNI } = require('./dni-validator');
describe('Validación DNI', () => {
test('DNI válido', () => {
const resultado = validarDNI('12345678Z');
expect(resultado.valido).toBe(true);
});
test('DNI formato inválido', () => {
const resultado = validarDNI('1234567');
expect(resultado.valido).toBe(false);
});
test('Letra incorrecta', () => {
const resultado = validarDNI('12345678A');
expect(resultado.valido).toBe(false);
});
});
Documentación con Swagger
const swaggerJsdoc = require('swagger-jsdoc');
const swaggerUi = require('swagger-ui-express');
const options = {
definition: {
openapi: '3.0.0',
info: {
title: 'API Validación DNI',
version: '1.0.0',
description: 'API para validar DNI españoles'
}
},
apis: ['./routes/*.js']
};
const specs = swaggerJsdoc(options);
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(specs));
Despliegue y Monitorización
Docker
FROM node:16-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]
Monitorización
- Logs estructurados
- Métricas de rendimiento
- Alertas de errores
- Dashboard de uso
Mejores Prácticas
Rendimiento
- Cache de resultados frecuentes
- Validación asíncrona
- Pooling de conexiones
- Compresión de respuestas
Escalabilidad
- Arquitectura sin estado
- Load balancing
- Auto-scaling
- CDN para recursos estáticos
La implementación de una API de validación DNI robusta requiere atención tanto a la precisión del algoritmo como a la seguridad y el rendimiento del sistema en producción.