Overview
The Photo Management system handles all aspects of photo storage, retrieval, comparison, and synchronization between the SIAD academic system and HikCentral biometric platform. This module ensures photo compatibility and maintains data integrity across both systems.
Photo Sources
The system manages photos from two distinct sources:
SIAD System Academic information system photos stored in SQL Server database as binary data (varbinary)
HikCentral Biometric system photos accessed via Artemis API with base64 encoding
Photo Retrieval
SIAD Photo Endpoints
Photos are retrieved directly from the database with proper caching:
Student Photos:
// Frontend request
getPhotoUrl ( ci ) {
const baseURL = API . defaults . baseURL ;
return ` ${ baseURL } /biometrico/fotografia/ ${ ci } ?t= ${ new Date (). getTime () } ` ;
}
Teacher/Staff Photos:
getPhotoUrl ( ci ) {
const baseURL = API . defaults . baseURL ;
return ` ${ baseURL } /biometrico/fotografiadoc/ ${ ci } ?t= ${ new Date (). getTime () } ` ;
}
The timestamp parameter (?t=) prevents browser caching and ensures the latest photo is always displayed after updates.
HikCentral Photo Endpoint
Photos from the biometric system are fetched via API:
getPhotoUrl2 ( ci ) {
const baseURL = API . defaults . baseURL ;
return ` ${ baseURL } /biometrico/gethick/ ${ ci } ?t= ${ new Date (). getTime () } ` ;
}
Backend Process:
Search person by ID in HikCentral
Retrieve PersonId (UUID)
Fetch face photo via Artemis API
Return image with proper headers
Photo Comparison
Facial Similarity Detection
The system uses backend image comparison to validate synchronization:
Retrieve Both Photos
Fetch photo from SIAD database and HikCentral API
Compare Images
Backend processing calculates similarity: // Frontend request
const { data } = await API . get ( `/biometrico/compare-hikdoc/ ${ cedula } ` );
// Response structure
{
"identicas" : true / false ,
"similitud" : "95.5" // Percentage
}
Display Results
Show similarity percentage and match status to user
Comparison Endpoints
For Students:
async ejecutarComparacion ( ci ) {
this . comparando = true ;
try {
const { data } = await API . get ( `/biometrico/compare-hikdoc-est/ ${ ci } ` );
this . comparacionResultado = data ;
if ( data . identicas ) {
console . log ( `✅ Match: ${ data . similitud } %` );
} else {
console . log ( `❌ Different: ${ data . similitud } % similarity` );
}
} finally {
this . comparando = false ;
}
}
For Teachers/Staff:
const { data } = await API . get ( `/biometrico/compare-hikdoc/ ${ ci } ` );
Visual Comparison Interface
Side-by-side photo display with clear source labeling:
< div class = "mb-4 flex justify-center" >
<!-- SIAD Photo -->
<div class="relative">
<img :src="getPhotoUrl(cedula)"
class="h-32 w-48 rounded-xl object-cover border-2" />
<span class="absolute -top-2 -right-2 bg-brand-500 text-white
text-[10px] px-2 py-1 rounded-full font-bold uppercase">SIAD</span>
</ div >
<!-- HikCentral Photo -->
< div class = "relative" >
<img :src="getPhotoUrl2(cedula)"
class="h-32 w-48 rounded-xl object-cover border-2" />
<span class="absolute -top-2 -right-2 bg-red-500 text-white
text-[10px] px-2 py-1 rounded-full uppercase">HIKCENTRAL</span>
</ div >
< / div >
Similarity Results Display
High Similarity
Low Similarity
Processing
< span class = "text-green-600 text-sm font-bold" >
Similitud: {{ comparacionResultado.similitud }}%
(Coincide)
</ span >
Validated Photos match - synchronization successful
< span class = "text-red-600 text-sm font-bold" >
Similitud: {{ comparacionResultado.similitud }}%
(No coincide)
</ span >
Update Required Photos don’t match - consider updating HikCentral photo
< div v-if = " comparando " class = "text-xs text-blue-500 animate-pulse" >
Calculando similitud facial...
</ div >
Loading indicator shown during comparison process
Bulk Photo Operations
Mass Download (ZIP Export)
Download all student/staff photos organized by academic program or personnel type:
async descargarDatosMasiva () {
this . cargando = true ;
const zip = new JSZip ();
// Single request for all photos
const response = await API . get ( '/biometrico/descargarfotosmasiva' , {
timeout: 600000 // 10 minutes
});
const registros = response . data ?. data || [];
for ( const post of registros ) {
// Decode base64 photo
const byteCharacters = atob ( post . fotografia );
const byteNumbers = new Array ( byteCharacters . length );
for ( let i = 0 ; i < byteCharacters . length ; i ++ ) {
byteNumbers [ i ] = byteCharacters . charCodeAt ( i );
}
const byteArray = new Uint8Array ( byteNumbers );
// Organize by academic program
const carreraNombre = post . NombCarr
? post . NombCarr . replace ( / [ \\ /:*?"<>| ] / g , "_" ). trim ()
: "Sin_Carrera" ;
const folder = zip . folder ( carreraNombre );
// Create filename
const nombre = post . NombInfPer . replace ( / \s + / g , " " ). trim ();
const apellido = post . ApellInfPer . replace ( / \s + / g , " " ). trim ();
const apellido2 = post . ApellMatInfPer . replace ( / \s + / g , " " ). trim ();
const fileName = ` ${ nombre } _ ${ apellido } _ ${ apellido2 } _ ${ post . CIInfPer } .jpg` ;
folder . file ( fileName , byteArray , { binary: true });
}
// Generate and download ZIP
const content = await zip . generateAsync ({
type: "blob" ,
compression: "DEFLATE" ,
compressionOptions: { level: 9 }
});
saveAs ( content , "Estudiantes_con_Foto_por_Carrera.zip" );
}
Mass download operations can take several minutes depending on the number of photos. The timeout is set to 10 minutes to accommodate large datasets.
ZIP Structure
For Students:
Estudiantes_con_Foto_por_Carrera.zip
├── Ingeniería en Sistemas/
│ ├── Juan_Perez_Garcia_1234567890.jpg
│ └── Maria_Lopez_Torres_0987654321.jpg
├── Administración de Empresas/
│ └── Carlos_Ruiz_Mendez_1122334455.jpg
└── Sin_Carrera/
└── Ana_Gomez_Vera_5544332211.jpg
For Teachers/Staff:
Docentes_con_Foto.zip
├── Docente/
│ └── Pedro_Martinez_Silva_1111111111.jpg
├── Administrativo/
│ └── Lucia_Fernandez_Ramos_2222222222.jpg
└── Trabajador/
└── Jose_Hernandez_Cruz_3333333333.jpg
Photo Upload to HikCentral
Upload Process
Photos are sent to HikCentral via the Artemis API:
Retrieve SIAD Photo
Fetch binary photo from SQL Server database
Convert to Base64
Encode photo to base64 format required by API
Prepare Person Data
Create person object with:
Name
ID number
Email
Organization ID
Photo data
Send to HikCentral
POST request to Artemis API with digital signature
Store PersonId
Save returned UUID for future updates
Student Upload
// Initial registration
const response = await API . post (
`/biometrico/sync-hikdoc-est-id/ ${ cedula } ` ,
{},
{ params: { idper: periodoSeleccionado } }
);
if ( response . data . code === "0" ) {
alert ( `✅ Registrado con éxito. ID: ${ response . data . data } ` );
}
Update Existing Photo
// Update requires PersonId from HikCentral
const response = await API . post (
`/biometrico/sync-hikdoc-update/ ${ cedula } ` ,
{ personaId: personIdHC },
{ params: { idper: periodoSeleccionado } }
);
Updates require the PersonId (UUID) from HikCentral, which is retrieved during the verification step.
Photo Validation
Compatibility Requirements
Photos must meet HikCentral standards:
Resolution Minimum dimensions for facial recognition
Quality Clear, frontal face visible
Background Plain background recommended
Validation Response
if ( response . data . code === "128" ) {
console . warn ( `La foto de ${ cedula } no es compatible con HikCentral.` );
alert ( "La foto no es compatible con HikCentral." );
this . estaRegistrado = false ;
}
Code 128 indicates photo rejection due to:
Poor quality
Face not detected
Incorrect format
Resolution too low
Error Handling
Image Loading Errors
handleImageError ( event ) {
// Fallback to default user icon
event . target . src =
"https://upload.wikimedia.org/wikipedia/commons/thumb/1/12/User_icon_2.svg/480px-User_icon_2.svg.png" ;
}
Applied to all photo <img> tags:
< img : src = " getPhotoUrl ( cedula ) " @ error = " handleImageError " />
Timeout Handling
try {
const response = await API . get ( '/biometrico/descargarfotosmasiva' , {
timeout: 600000
});
} catch ( error ) {
if ( error . code === "ECONNABORTED" || error . message . includes ( "timeout" )) {
alert ( "La conexión expiró. El proceso es muy pesado. Inténtelo de nuevo." );
}
}
Cache Management
Frontend Cache Busting
Timestamp parameters prevent stale images:
getPhotoUrl ( ci ) {
const timestamp = new Date (). getTime ();
return ` ${ baseURL } /biometrico/fotografia/ ${ ci } ?t= ${ timestamp } ` ;
}
// Alternative using refreshKey
data () {
return {
refreshKey: Date . now ()
}
}
getPhotoUrl ( ci ) {
return ` ${ baseURL } /biometrico/fotografia/ ${ ci } ?v= ${ this . refreshKey } ` ;
}
Force Cache Refresh
Manual cache clearing for specific users:
async forzarRefrescoFoto ( ci ) {
this . cargandoStatus = true ;
// Update refresh key
this . refreshKey = Date . now ();
// Optional: Backend cache clear endpoint
await API . get ( `/biometrico/clear-cache/ ${ ci } ` );
// Re-verify registration
await this . verificarRegistroHC ( ci );
}
Best Practices
Photo Management Guidelines
Always validate photos before bulk uploads
Use timestamp parameters to prevent cache issues
Monitor similarity scores after synchronization
Handle errors gracefully with fallback images
Implement timeouts for large batch operations
Organize exports by program/department for easier management
Verify photo quality meets HikCentral requirements before upload
Cache PersonId values to enable updates without re-registration
Rate Limiting : Bulk operations include 50-300ms delays between requests to prevent server overload and 429 (Too Many Requests) errors.
// Example rate limiting in bulk verification
for ( let post of items ) {
const res = await API . get ( `/biometrico/getperson/ ${ post . CIInfPer } ` );
post . estaRegistradoHC = res . data . registrado ;
await new Promise ( resolve => setTimeout ( resolve , 50 ));
}
Photo retrieval from SIAD is optimized with database indexing on the ID field (CIInfPer) for fast lookups.