Alcance y modelo de confianza
El diseño es "trust-minimizing": AsisteFácil publica los datos y la constancia; cualquier tercero recalcula todo por su cuenta. Si algo no cuadra, el recálculo lo delata — no hace falta creerle a AsisteFácil.
Qué prueba este protocolo
| Integridad | Un movimiento incluido en un sello no ha sido alterado desde entonces. Cualquier cambio a un campo cubierto por el hash rompe la hoja y, por lo tanto, la raíz del árbol. |
| Completitud del día | No se añadieron ni quitaron movimientos del conjunto del día después del sellado — cambiar el número de hojas cambia la raíz. |
| Anterioridad temporal | La raíz —y todo el conjunto de movimientos que representa— existía antes del sello de tiempo, emitido por un tercero acreditado e independiente de AsisteFácil. |
| No repudio del certificador | La constancia está firmada con el certificado del Prestador de Servicios de Certificación (PSC); su validez se comprueba contra el propio PSC y contra cadenas de confianza públicas. |
Qué no prueba
Que la checada corresponda a una persona física real presente en un lugar — eso lo aportan la foto, la geolocalización y el resto de controles del producto. Tampoco prueba nada anterior a la fecha del primer sello, ni la existencia de un movimiento que nunca se incluyó en un sello.
Quién necesita confiar en quién
| Parte | Hace falta confiar en… | No hace falta confiar en… |
|---|---|---|
| AsisteFácil | nada, para la verificación criptográfica | — |
| El PSC | su acreditación y su firma | su disponibilidad continua — la constancia se verifica sin conexión |
| El perito / tercero | sus propias herramientas (`sha256sum`, un visor ASN.1) | — |
Estructura de los datos
Cada checada se convierte en un objeto JSON plano con un esquema fijo y versionado (identificado como MOV-1). Es exactamente lo que ya se muestra en la ficha pública de cualquier movimiento:
{
"key": "identificador público del movimiento",
"fecha": "YYYY-MM-DD",
"hora": "HH:MM:SS",
"creadoEl": "YYYY-MM-DD HH:MM:SS",
"empleadoId": entero,
"empleado": "nombre completo",
"tipoAsistenciaId": "entero — 1 = entrada, 2 = salida",
"tipoAsistencia": "texto",
"centroDeTrabajoId": entero,
"centroDeTrabajo": "texto",
"latitud": "cadena de texto",
"longitud": "cadena de texto",
"imagenArchivo": "nombre del archivo de la foto",
"imagenHash": "SHA-256 (hex) de la foto",
"altaManual": booleano,
"origenRegistroId": entero,
"origenRegistro": "texto",
"registranteId": entero,
"registrante": "texto",
"hashVersion": "MOV-1",
"hashAlgoritmo": "SHA-256",
"hashAnterior": "hoja del movimiento inmediatamente anterior, o null"
}
tipoAsistenciaId es el dato capturado al momento del registro y ya inmutable — forma parte de lo que se sella. Cualquier reclasificación operativa posterior se maneja en un campo aparte que queda fuera del hash, precisamente para que reclasificar un movimiento no invalide su evidencia original.
Latitud y longitud se guardan como texto, no como número, para preservar exactamente la precisión capturada sin ambigüedad de redondeo.
El árbol diario (MK-1)
Cada día, por empresa, se consolida un árbol de Merkle cuya raíz es lo único que se sella ante el PSC. El objeto que respalda ese árbol incluye la especificación, la raíz en hexadecimal y en Base64, el número de hojas, y el árbol completo nivel por nivel — todo publicado junto con la constancia.
MOV-1 completo tal como lo vería cualquier visitante, incluyendo su HASH.Algoritmo de canonicalización
Antes de hashear, el objeto JSON se convierte a una representación determinista, para que cualquier reimplementación en cualquier lenguaje produzca, byte a byte, la misma cadena.
Reglas
| Orden de llaves | Ascendente, comparación de cadenas byte a byte (equivalente a strcmp; para llaves ASCII, orden de código de punto). |
| Espacios | Ninguno. {"a":1,"b":2}, nunca {"a": 1, "b": 2}. Sin saltos de línea ni sangría. |
| Unicode | Caracteres multibyte (acentos, ñ) se emiten literales en UTF-8, no como secuencias de escape. |
Diagonales / | No se escapan. |
| Comillas y barra invertida | " → \", \ → \\. Caracteres de control se escapan. |
| Nulos y booleanos | null, true, false literales, sin comillas. |
Vector de prueba (normativo)
Objeto de ejemplo:
{"key":"Ab12Cd34","fecha":"2026-09-08","hora":"08:01:15","creadoEl":"2026-09-08 08:01:17","empleadoId":42,"empleado":"María Núñez","tipoAsistenciaId":1,"tipoAsistencia":"Entrada","centroDeTrabajoId":7,"centroDeTrabajo":"Obra Centro","latitud":"19.4326","longitud":"-99.1332","imagenArchivo":"Ab12Cd34.jpg","imagenHash":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","altaManual":false,"origenRegistroId":2,"origenRegistro":"App","registranteId":1,"registrante":"María Núñez","hashVersion":"MOV-1","hashAlgoritmo":"SHA-256","hashAnterior":null}
Cadena canónica (llaves reordenadas):
{"altaManual":false,"centroDeTrabajo":"Obra Centro","centroDeTrabajoId":7,"creadoEl":"2026-09-08 08:01:17","empleado":"María Núñez","empleadoId":42,"fecha":"2026-09-08","hashAlgoritmo":"SHA-256","hashAnterior":null,"hashVersion":"MOV-1","hora":"08:01:15","imagenArchivo":"Ab12Cd34.jpg","imagenHash":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","key":"Ab12Cd34","latitud":"19.4326","longitud":"-99.1332","origenRegistro":"App","origenRegistroId":2,"registrante":"María Núñez","registranteId":1,"tipoAsistencia":"Entrada","tipoAsistenciaId":1}
SHA-256 esperado:
637f7f91b4a751d5948e04385dcb4ba0b9f01bf6d7cf240b10f63fac52a3dad7
Algoritmo de hash
- Función: SHA-256 (FIPS 180-4).
- Codificación por defecto: hexadecimal en minúsculas, 64 caracteres.
- Codificación alterna: Base64 de los 32 bytes crudos — se usa únicamente al comunicarse con el PSC.
- No se usa sal, HMAC ni derivación de llave en ningún punto: el objetivo es integridad verificable por cualquiera, no confidencialidad.
- La fotografía se hashea también con SHA-256, sobre los bytes del archivo tal como se almacenó.
Encadenamiento de movimientos
Cada movimiento incorpora, dentro del propio objeto que se hashea, el campo hashAnterior: la hoja del movimiento inmediatamente anterior del sistema.
El efecto es una cadena: hoja_n = SHA-256( … ‖ hoja_{n-1} ). Alterar o intentar retirar un movimiento antiguo invalida, en cascada, todas las hojas posteriores — y por lo tanto todas las raíces diarias siguientes, no solo la de su propio día.
Construcción del árbol de Merkle
Un árbol independiente por empresa y por día. Las hojas son la huella de cada movimiento del día calendario (incluidos los marcados como eliminados, porque un hecho que ocurrió sigue siendo un hecho), en el orden en que se dieron de alta.
mientras el nivel tenga más de un nodo:
para cada par de nodos (izquierdo, derecho):
si no hay derecho (nivel impar): derecho = izquierdo // se duplica el último
padre = SHA-256( izquierdo + derecho ) // concatenación de los hex
siguiente_nivel = [ todos los padres ]
el nivel que queda con un solo nodo es la raíz
Esa raíz —y solo esa raíz— es lo que se sella ante el PSC.
Formato de la prueba de inclusión
Cualquier hoja puede verificarse contra la raíz sin necesitar el árbol completo, con una lista ordenada de "hashes hermanos" y su posición:
[
{ "hash": "<hex del hermano>", "posicion": "izquierda|derecha" },
… // un elemento por nivel, de la hoja hacia la raíz
]
Verificación: partiendo del hash de la hoja, en cada paso se calcula SHA-256(acumulado + hermano) o SHA-256(hermano + acumulado) según la posición indicada, hasta llegar a la raíz y compararla con la raíz sellada. Esto ya está disponible públicamente en la ficha de cada árbol, y se puede recalcular en cualquier navegador sin llamar a ningún servidor de AsisteFácil.
Qué se envía exactamente al certificador
Únicamente la raíz diaria de cada empresa — 32 bytes de SHA-256. Nunca se envían datos personales, fotografías, ni el árbol completo. Para el Prestador de Servicios de Certificación, la raíz es indistinguible del hash de cualquier otro archivo: no tiene forma de saber qué representa.
Qué se recibe: la constancia NOM-151
La constancia es un objeto ASN.1/DER firmado (CMS/PKCS#7 SignedData con sello de tiempo conforme a NOM-151 / RFC 3161), que contiene el hash sellado, la marca de tiempo confiable, la firma del PSC y su cadena de certificados.
Adicionalmente se genera un PDF de "evidencia", pensado para lectura humana — es informativo y no sustituye a la constancia, que es la prueba primaria.
Cómo se verifica
La verificación completa tiene cinco niveles. Los primeros tres son criptografía pura que cualquiera puede reproducir sin depender de nadie; los dos últimos se apoyan en el certificador.
| # | Qué se comprueba | Cómo |
|---|---|---|
| 1 | Cada hoja corresponde a los datos del movimiento | Recalcular el objeto canónico y su SHA-256 |
| 2 | El árbol reduce correctamente las hojas a la raíz | Reconstruir el árbol completo |
| 3 | La raíz coincide con la publicada | Comparación directa |
| 4 | La raíz está contenida en la constancia | Validador del PSC o un visor ASN.1 |
| 5 | La constancia la firmó un PSC acreditado y su sello de tiempo es válido | Validación de firma contra cadenas de certificados públicas |
Las fichas públicas de AsisteFácil ya hacen el nivel 1-3 en el propio navegador de quien las visita, usando únicamente funciones criptográficas nativas — sin llamar a ningún servidor.
Cómo un tercero reconstruye todo sin confiar en AsisteFácil
Para verificar un día completo, un perito necesita: la constancia (.asn1), el objeto del árbol (JSON), la raíz, y el objeto MOV-1 de cada movimiento incluido. Todo descargable desde la ficha pública correspondiente — por ejemplo, este Merkle con seis movimientos, que incluye el archivo .asn1 para descarga directa.
Script de referencia (Python)
import hashlib
def sha256_hex(s: str) -> str:
return hashlib.sha256(s.encode("utf-8")).hexdigest()
def merkle_root(hojas: list[str]) -> str | None:
if not hojas:
return None
nivel = [h.lower() for h in hojas]
while len(nivel) > 1:
siguiente = []
for i in range(0, len(nivel), 2):
izq = nivel[i]
der = nivel[i + 1] if i + 1 < len(nivel) else izq # impar: duplica
siguiente.append(sha256_hex(izq + der))
nivel = siguiente
return nivel[0]
# assert merkle_root(hojas) == raiz_publicada
Con la raíz reconstruida, el último paso es independiente de AsisteFácil por completo: subir la constancia y la raíz (en Base64) al validador público del PSC, que confirma si esa raíz está contenida en la constancia y si la firmó el certificador acreditado.
¿Necesitas mostrarle esto a tu área legal o a un perito?
Con gusto te compartimos una constancia real (ambiente de pruebas) para que la validen con su propia herramienta, sin depender de la nuestra.
Solicitar una demostración