Si construyes un sistema con reconocimiento facial en México y lo haces mal, el Artículo 69 de la LFPDPPP contempla hasta cinco años de prisión. No es un susto retórico — es texto de ley, vigente, aplicable a personas físicas, y con precedentes reales de sanciones emitidas por el INAI. Este artículo explica qué dice la ley, cómo la rompen la mayoría de sistemas en producción, y qué implementar en código para cumplir sin complicar tu arquitectura.
Qué es la LFPDPPP en 3 minutos
La Ley Federal de Protección de Datos Personales en Posesión de los Particulares — LFPDPPP para los cuates — se publicó en 2010 y regula cómo las empresas privadas mexicanas tratan datos personales. Aplica a cualquier empresa que capture información de personas identificables: un email en un formulario ya cuenta.
La autoridad que enforcea es el Instituto Nacional de Transparencia, Acceso a la Información y Protección de Datos Personales (INAI). Las sanciones son en dos capas:
- Administrativas (Art. 63-66) — multas de 100 a 320,000 UMA (~$11k a $36M MXN en 2026).
- Penales (Art. 67-69) — prisión de 3 meses a 5 años para violaciones dolosas, especialmente con datos sensibles.
La responsabilidad es solidaria: la empresa responde administrativamente, pero quien toma decisiones técnicas puede ser imputado penalmente si la violación fue deliberada o por omisión grave.
Los 5 artículos que importan
De las 69 secciones de la ley, hay cinco que todo dev en México debería saber de memoria:
Artículo 8 — Consentimiento
Requiere consentimiento del titular para tratar sus datos. Puede ser tácito (dejar visible el aviso de privacidad) o expreso (declaración afirmativa del usuario). Para datos normales — email, nombre, teléfono — el tácito alcanza.
Artículo 9 — Consentimiento expreso y por escrito para datos sensibles
Aquí empieza el infierno. El Art. 9 exige consentimiento expreso y por escrito para datos sensibles — categoría que incluye:
- Datos biométricos (huella, rostro, iris, voz)
- Estado de salud presente y futura
- Origen racial o étnico
- Creencias religiosas, filosóficas y morales
- Afiliación sindical
- Opiniones políticas
- Preferencia sexual
"Por escrito" no significa papel — incluye firma electrónica, pero requiere manifestación inequívoca y afirmativa. Un checkbox pre-marcado no vale. Un checkbox vacío que el usuario marca sí.
Artículo 11 — Calidad y retención
Los datos solo pueden tratarse el tiempo necesario para cumplir la finalidad. Después, el responsable debe cancelarlos. "Por si acaso" no es una finalidad válida.
Artículos 22-28 — Derechos ARCO
El titular puede ejercer cuatro derechos:
- Acceso: saber qué datos tienes de él.
- Rectificación: corregir datos incorrectos.
- Cancelación: borrarlos.
- Oposición: parar un tratamiento específico manteniendo otros.
Tienes 20 días hábiles para responder. Sin excepciones.
Artículo 69 — La pena de prisión que casi nadie lee
Se sancionará con prisión de seis meses a cinco
años al que, con el fin de alcanzar un lucro
indebido, trate datos personales sensibles
mediando engaño, aprovechándose del error en
que se encuentre el titular o la persona
autorizada para transmitirlos.La pena se agrava con datos sensibles y cuando hay lucro. Si tu startup captura rostros sin consentimiento expreso y vende analytics basados en esa información, cumples los tres requisitos: datos sensibles, engaño (ausencia de aviso claro), lucro. Cinco años.
Los devs mexicanos subestiman el Artículo 9 porque no viene con una cookie banner. Pero capturar un descriptor facial sin firma del titular es técnicamente el mismo delito que venderlo.
Consentimiento como arquitectura, no como checkbox
La implementación más común — un checkbox con "Acepto los términos" — falla el Art. 9 por dos razones:
- No es expreso si está en medio de otros términos legales mezclados.
- No es por escrito si no guardas evidencia criptográfica de qué versión del aviso aceptó, cuándo, y desde qué IP/dispositivo.
La implementación que sí cumple tiene cuatro componentes mínimos:
// Firestore: consent_pending/{userId}
{
userId: "uid-123",
avisoVersion: "v2.1-2026-04", // versionado
acceptedAt: Timestamp, // cuándo
ipHash: "sha256:...", // desde dónde (hash, no raw)
userAgent: "Chrome 134 / Android 15", // en qué dispositivo
signatureMethod: "touch" | "checkbox" | "otp",
specificScopes: ["biometria", "comunicacion", "analytics"],
// Si alguno es 'biometria' → requiere flujo Art. 9 adicional
tutorSigned: boolean, // menor de edad
tutorUid?: string,
revokedAt?: Timestamp, // si cancelación posterior
}El aviso de privacidad vive como un documento versionado que nunca se edita retroactivamente. Si cambias los términos, subes v2.2 y todos los usuarios anteriores siguen registrados contra v2.1 hasta que acepten la nueva.
Biometría: qué NO hacer
Tres anti-patterns que hemos visto en auditorías:
1. Guardar imágenes del rostro
La foto del alumno en Firebase Storage, el selfie de KYC en S3, el frame capturado por el kiosco subido "por si acaso". Todo esto es dato biométrico bruto. Cada uno es una mina antipersonal legal. Si hay una brecha, la sanción por dato personal común es una cosa; por dato sensible se multiplica, y si hubo negligencia grave, entran penales.
2. Descriptores sin cifrar
"Bueno, no guardamos la foto, solo un descriptor de 128 floats". El descriptor es dato biométrico según el Art. 3-VI. Guardarlo en Firestore o Postgres en claro es tan grave como guardar el original.
3. Retención indefinida
El Art. 11 exige cancelación al agotar la finalidad. Un alumno que egresó hace 3 años no necesita estar en tu sistema de acceso. Un empleado despedido tampoco. La política mínima: eliminación automática al evento que agota la finalidad. Si implementas esto tarde, el auditor lo ve en la retención del primer registro.
Biometría: cómo sí hacerlo
En KEYON Access implementamos el flujo completo. El código está abierto y cumple Art. 9 + Art. 14 + Art. 19. Los cuatro componentes:
1. Solo descriptores, nunca imágenes
El frame se procesa localmente (edge), se extrae un descriptor de 128 dimensiones, y el frame se descarta inmediatamente. Nunca toca disco, nunca toca red.
2. Cifrado AES-GCM 256 con clave derivada
Cada escuela deriva su propia clave maestra con PBKDF2-HMAC-SHA256, 150,000 iteraciones, usando un salt único. La clave nunca se persiste — se deriva en memoria cuando un usuario autenticado la necesita:
async function initSchoolKey(schoolId) {
const doc = await db.collection("config").doc("crypto").get();
const { salt, passphraseHash } = doc.data()[schoolId];
// passphrase viene del superadmin autenticado — nunca en código
const passphrase = await requireSuperAdminInput();
const baseKey = await crypto.subtle.importKey(
"raw",
new TextEncoder().encode(passphrase),
{ name: "PBKDF2" },
false,
["deriveKey"]
);
return crypto.subtle.deriveKey(
{
name: "PBKDF2",
salt: new TextEncoder().encode(salt), // UTF-8 de string base64
iterations: 150_000,
hash: "SHA-256",
},
baseKey,
{ name: "AES-GCM", length: 256 },
false, // no extractable
["encrypt", "decrypt"]
);
}El salt se pasa a PBKDF2 como el UTF-8 del string base64, NO como los bytes decodificados del base64. Es el error que nos tomó tres días encontrar — el descifrado daba bytes aleatorios aunque todo parecía correcto. Si el salt de registro y el salt de verificación no son exactamente la misma representación, los hashes no coinciden.
3. Cifrado del descriptor + IV único por registro
async function encryptDescriptor(descriptor128) {
const key = await getSchoolKey();
const iv = crypto.getRandomValues(new Uint8Array(12)); // 96-bit IV
const ciphertext = await crypto.subtle.encrypt(
{ name: "AES-GCM", iv, tagLength: 128 },
key,
descriptor128.buffer
);
return {
ct: arrayBufferToBase64(ciphertext),
iv: arrayBufferToBase64(iv),
v: 1, // esquema versionado para poder rotar
};
}El IV es aleatorio por cada cifrado y se guarda con el ciphertext — nunca se reusa (eso rompería AES-GCM catastróficamente). El campo v permite rotar el esquema sin romper registros viejos.
4. Retención con eliminación verificable
// Cloud Function programada diariamente
export const purgeExpiredBiometrics = functions.pubsub
.schedule("0 3 * * *") // 3 AM todos los días
.onRun(async () => {
const now = Timestamp.now();
const cutoff = new Date(now.toDate());
cutoff.setDate(cutoff.getDate() - 30); // 30 días después de egreso
const snap = await db
.collection("alumnos")
.where("estado", "==", "egresado")
.where("fechaEgreso", "<", cutoff)
.where("reconocimientoFacial.encryptedDescriptor", "!=", null)
.get();
for (const doc of snap.docs) {
await doc.ref.update({
reconocimientoFacial: FieldValue.delete(),
eliminacionBiometrica: {
fecha: now,
razon: "retention-art-11",
firmaCrypto: await signDeletion(doc.id, now),
},
});
}
});El registro de eliminación queda firmado criptográficamente — si un auditor pide evidencia de que cumpliste Art. 11, le muestras la entrada con firma que no puedes falsificar.
Derechos ARCO: cada derecho es un endpoint
Los cuatro derechos ARCO no son una página de aviso — son funcionalidad real que el titular debe poder ejercer. Implementación mínima:
// Acceso — Art. 22-24
GET /api/arco/export → ZIP con todos los datos del usuario
// Rectificación — Art. 25
PATCH /api/users/me → actualiza datos editables
// Cancelación — Art. 26
DELETE /api/users/me → eliminación real + purga de backups
(no soft delete con flag)
// Oposición — Art. 27
POST /api/arco/opt-out → desactiva tratamientos específicos
manteniendo otros (ej: no analytics,
pero sí registro de acceso)La implementación incorrecta más común es la Cancelación como soft delete: pones deleted: true en el documento y listo. Eso no cumple. El Art. 26 exige supresión efectiva, incluyendo backups. Una política razonable: hard delete de Firestore + purga de backups de más de 30 días.
Auditoría: qué loggear, qué NO loggear
Dos logs separados con propósitos distintos:
Log de aplicación (Sentry, Datadog, Cloud Logging)
Nunca incluye datos personales. Emails, nombres, matrículas — todos están prohibidos aquí. Si necesitas correlación, usa user IDs internos o hashes. El log de aplicación es público dentro del equipo de ingeniería, y las 2am pueden ocurrir fugas accidentales a Slack.
// ❌ MAL — datos en log
logger.info("Alumno inició sesión", {
email: user.email, // NO
nombre: user.nombre, // NO
});
// ✓ BIEN — IDs internos
logger.info("session.login", {
uid: user.uid, // hash interno, no identificable solo
schoolId: user.schoolId,
role: user.role,
});Audit log de negocio (Firestore, tabla dedicada)
Este sí contiene datos personales, controla accesos (quién leyó qué), y se retiene 5 años por defecto. Campos mínimos:
audit_log/{id}
{
actorUid: string, // quién ejecutó la acción
action: "READ" | "UPDATE" | "DELETE" | "EXPORT",
targetCollection: "alumnos",
targetId: string, // sobre qué recurso
changes?: Record<string, any>, // diff si es UPDATE
timestamp: Timestamp,
ip: string, // con consentimiento
reason?: string, // justificación si es consulta masiva
}Los 5 errores que más ven los auditores
En conversaciones con consultores de compliance que auditan empresas mexicanas, estos son los findings repetidos:
- Aviso de privacidad genérico de plantilla. Copy-paste del modelo del INAI sin personalizar finalidades, transferencias, ni designación de responsable.
- Consentimiento implícito para biometría. "Al usar el sistema aceptas..." — inválido para Art. 9.
- Logs de aplicación con PII. Emails, teléfonos, nombres en Cloud Logging sin retención acotada ni ACL.
- Retención indefinida. Alumnos egresados con descriptores biométricos vigentes por años.
- Sin flujo ARCO implementado. El aviso menciona ARCO pero el usuario no tiene UI ni endpoint para ejercerlos.
Cumplir no es opcional, y es barato
Una tarde de trabajo bien invertida te quita 95 % del riesgo regulatorio:
- 1-2 horas escribiendo aviso de privacidad específico a tu caso.
- 2-3 horas implementando flujo de consentimiento con versionado.
- 2-3 horas agregando endpoints ARCO.
- 1-2 horas auditando logs y removiendo PII.
Es menos de un sprint. La alternativa — sanciones administrativas que van de $11k a $36M MXN, o prisión de hasta 5 años por tratamiento doloso de datos sensibles — es el peor ROI negativo imaginable.
Si tu sistema maneja biometría, datos de salud, orientación sexual o cualquier otro dato del Art. 9, y no has hecho el ejercicio, empieza hoy. Si necesitas una revisión pragmática de tu arquitectura actual desde compliance, escríbenos — cada deploy nos ha enseñado un pozo nuevo que ya documentamos.