Overview
Kontrak Backend can generate three types of employment contracts:
PLANILLA : Full-time contracts with all legal benefits
PART TIME : Part-time employment contracts
SUBSIDIO : Temporary replacement contracts
Contracts are generated as PDF files and can be downloaded individually or in bulk as a ZIP archive.
Contract Types
PLANILLA - Full-Time Contract
Standard employment contract with all legal benefits. Required Fields:
Basic employee information (name, DNI, address)
Position and subdivision/parking
Salary information
Entry and end dates
Location details (province, district, department)
PART TIME - Part-Time Contract
Part-time employment with reduced hours. Required Fields:
Same as PLANILLA contract
No additional fields required
SUBSIDIO - Replacement Contract
Temporary contract for replacing another employee. Required Fields:
All PLANILLA fields
Additional fields:
replacementFor: Name of employee being replaced
reasonForSubstitution: Reason for the replacement
timeForCompany: Time in company (optional)
workingCondition: Working condition (optional)
probationaryPeriod: Probationary period (optional)
Generating Contracts in Bulk
Step 1: Prepare Employee Data
You can upload employee data via Excel file or send it directly as JSON.
Via Excel Upload
Via JSON API
Upload an Excel file with employee data: curl -X POST http://localhost:3000/api/excel/upload \
-F "[email protected] " \
-H "Content-Type: multipart/form-data"
Response: {
"success" : true ,
"message" : "Excel procesado correctamente" ,
"data" : {
"employees" : [
{
"name" : "Juan" ,
"lastNameFather" : "Pérez" ,
"lastNameMother" : "García" ,
"dni" : "12345678" ,
"contractType" : "Planilla" ,
// ... other fields
}
],
"totalRecords" : 10
}
}
See Excel Upload Guide for Excel format details. Send employee data directly as JSON: curl -X POST http://localhost:3000/api/contract/download-zip \
-H "Content-Type: application/json" \
-d '{
"body": [
{
"name": "Juan",
"lastNameFather": "Pérez",
"lastNameMother": "García",
"dni": "12345678",
"email": "[email protected] ",
"birthDate": "15/03/1990",
"sex": "M",
"address": "Av. Principal 123",
"province": "Lima",
"district": "Miraflores",
"department": "Lima",
"salary": 3000,
"salaryInWords": "tres mil soles",
"position": "Analista",
"entryDate": "01/01/2024",
"endDate": "31/12/2024",
"subDivisionOrParking": "Sede Central",
"contractType": "Planilla"
}
]
}'
Step 2: Download ZIP Archive
Send batch request
Send the employee data array to the /contract/download-zip endpoint: curl -X POST http://localhost:3000/api/contract/download-zip \
-H "Content-Type: application/json" \
-d @employees.json \
--output contracts.zip
Receive ZIP file
The response is a ZIP file containing:
Contract PDFs for each employee
Data processing consent PDFs
Annex documents
ZIP Structure: contracts.zip
├── planilla/
│ ├── 12345678.pdf
│ ├── Tratamiento de datos/
│ │ └── 12345678.pdf
│ └── Anexos/
│ └── 12345678.pdf
├── parttime/
│ └── ...
└── subsidio/
└── ...
Extract and use
Extract the ZIP file and distribute contracts: unzip contracts.zip -d output/
Generating Single Contracts
Preview Contract (Base64)
Get a contract preview as base64 encoded string:
curl -X POST http://localhost:3000/api/contract/preview \
-H "Content-Type: application/json" \
-d '{
"sessionId": "abc123",
"dni": "12345678",
"format": "base64"
}'
Response:
{
"success" : true ,
"data" : {
"dni" : "12345678" ,
"pdfBase64" : "JVBERi0xLjQKJeLjz9MKMyAwIG9iago8..." ,
"fileName" : "12345678.pdf"
}
}
Download Contract File
Get contract as downloadable PDF:
curl -X POST http://localhost:3000/api/contract/preview \
-H "Content-Type: application/json" \
-d '{
"sessionId": "abc123",
"dni": "12345678",
"format": "file"
}' \
--output contract.pdf
Contract Generation Process
The contract generation process (src/services/contract.service.ts:30) follows these steps:
Validation
Employee data is validated against the contract type schema.
PDF Generation
Three PDFs are generated in parallel:
Main employment contract
Personal data processing consent
Contract annexes
Archive Creation
PDFs are organized by contract type and packaged into a ZIP file.
Streaming Response
ZIP file is streamed directly to the client without saving to disk.
// Simplified contract generation logic
for ( const emp of employee ) {
const rootFolder = emp . contractType . toLowerCase (). replace ( / \s + / g , '' );
const [ contractResult , processingOfPresonalDataPDF , anexoDocResult ] =
await Promise . all ([
this . pdfGeneratorService . generateContract ( emp , emp . contractType , browser ),
generateProcessingOfPresonalDataPDF ( emp ),
generateDocAnexo ( emp , browser ),
]);
archive . append ( contractResult . buffer , {
name: ` ${ rootFolder } / ${ contractResult . filename } ` ,
});
archive . append ( processingOfPresonalDataPDF , {
name: ` ${ rootFolder } /Tratamiento de datos/ ${ emp . dni } .pdf` ,
});
archive . append ( anexoDocResult , {
name: ` ${ rootFolder } /Anexos/ ${ emp . dni } .pdf` ,
});
}
Data Validation
All employee data is validated before contract generation. See the validation schema in src/validators/employee.validator.ts:33.
Common Validation Rules
Name Fields
DNI
Email
Salary
Dates
name : z . string ()
. min ( 2 , 'Nombre debe tener al menos 2 letras' )
. max ( 50 , 'Nombre debe tener como maximo 50 letras' )
. regex ( / ^ [ a-zA-ZáéíóúÁÉÍÓÚñÑ\s ] + $ / , 'Nombre solo puede contener letras' )
SUBSIDIO Contract Special Validation
SUBSIDIO contracts require additional fields that are validated conditionally.
. superRefine (( data , ctx ) => {
if ( data . contractType === 'Subsidio' ) {
if ( ! data . replacementFor ) {
ctx . addIssue ({
message: "El campo 'SUPLENCIA DE' es obligatorio para contratos de Subsidio" ,
path: [ 'replacementFor' ],
});
}
if ( ! data . reasonForSubstitution ) {
ctx . addIssue ({
message: "El campo 'MOTIVO DE SUPLENCIA' es obligatorio para contratos de Subsidio" ,
path: [ 'reasonForSubstitution' ],
});
}
}
})
Batch Size Limits
The API enforces batch size limits to prevent resource exhaustion.
Minimum : 1 employee
Maximum : 50 employees per request (configurable in validation schema)
Recommended : Keep batches under 20 employees for optimal performance
export const EmployeeBatchSchema = z . object ({
body: z . array ( employeeSchema )
. min ( 1 , 'Debes seleccionar al menos un empleado para generar el ZIP' )
. max ( 50 , 'Máximo 50 empleados por lote' ),
});
Error Handling
If contract generation fails for individual employees, the process continues for others:
try {
// Generate contract PDFs
} catch ( error ) {
if ( error instanceof Error ) {
logger . info ( `Error con empleado ${ emp . dni } : ${ error . message } ` );
}
// Continue with next employee
}
See Error Handling Guide for more details.
Complete Example
Generate Contracts for Multiple Employees
curl -X POST http://localhost:3000/api/contract/download-zip \
-H "Content-Type: application/json" \
-d '{
"body": [
{
"name": "Juan",
"lastNameFather": "Pérez",
"lastNameMother": "García",
"dni": "12345678",
"email": "[email protected] ",
"birthDate": "15/03/1990",
"sex": "M",
"address": "Av. Principal 123",
"province": "Lima",
"district": "Miraflores",
"department": "Lima",
"salary": 3000,
"salaryInWords": "tres mil soles",
"position": "Analista",
"entryDate": "01/01/2024",
"endDate": "31/12/2024",
"subDivisionOrParking": "Sede Central",
"contractType": "Planilla"
},
{
"name": "María",
"lastNameFather": "López",
"lastNameMother": "Ruiz",
"dni": "87654321",
"email": "[email protected] ",
"birthDate": "20/07/1988",
"sex": "F",
"address": "Jr. Los Olivos 456",
"province": "Lima",
"district": "San Isidro",
"department": "Lima",
"salary": 2500,
"salaryInWords": "dos mil quinientos soles",
"position": "Asistente",
"entryDate": "15/02/2024",
"endDate": "31/08/2024",
"subDivisionOrParking": "Oficina Principal",
"contractType": "Subsidio",
"replacementFor": "Carlos Mendoza",
"reasonForSubstitution": "Vacaciones",
"timeForCompany": "6 meses",
"workingCondition": "Temporal",
"probationaryPeriod": "3 meses"
}
]
}' \
--output contracts.zip
Next Steps
Excel Upload Learn how to prepare Excel files for bulk upload
Error Handling Understand error responses and debugging