Documentation Index
Fetch the complete documentation index at: https://mintlify.com/ashcroft08/provesa-web/llms.txt
Use this file to discover all available pages before exploring further.
The Empleo Service manages employment branch locations (sucursales) that are displayed in job application forms.
Overview
This simple service manages a list of branch locations where candidates can apply for jobs. Branches can be activated or deactivated to control which locations appear in the public job application form.
Dependencies
empleoRepository - Database operations for employment branches
Methods
getAll()
Retrieves all employment branches, ordered alphabetically by name.
import { empleoService } from '$lib/server/services/empleo.service.js';
const sucursales = await empleoService.getAll();
Array of branch objects:
id: Branch identifier (number)
nombre: Branch name
activa: Boolean indicating if branch is active
Example Response
[
{
id: 1,
nombre: 'Asunción Centro',
activa: true
},
{
id: 2,
nombre: 'Ciudad del Este',
activa: true
},
{
id: 3,
nombre: 'Encarnación',
activa: false
}
]
Implementation Details
Source: src/lib/server/services/empleo.service.js:5-7
Branches are ordered alphabetically by nombre field.
getActive()
Retrieves only active employment branches.
import { empleoService } from '$lib/server/services/empleo.service.js';
const activeSucursales = await empleoService.getActive();
Array of active branch objects (same structure as getAll)
Use this method for public-facing job application forms to show only available branches.
Example Response
[
{
id: 1,
nombre: 'Asunción Centro',
activa: true
},
{
id: 2,
nombre: 'Ciudad del Este',
activa: true
}
]
Implementation Details
Source: src/lib/server/services/empleo.service.js:10-12
addSucursal()
Creates a new employment branch.
import { empleoService } from '$lib/server/services/empleo.service.js';
await empleoService.addSucursal('Fernando de la Mora');
New branches are created as inactive (activa: false) by default.
Implementation Details
Source: src/lib/server/services/empleo.service.js:15-17
toggleSucursal()
Activates or deactivates an employment branch.
import { empleoService } from '$lib/server/services/empleo.service.js';
// Activate branch
await empleoService.toggleSucursal(3, true);
// Deactivate branch
await empleoService.toggleSucursal(3, false);
New active status (true = active, false = inactive)
Implementation Details
Source: src/lib/server/services/empleo.service.js:20-22
deleteSucursal()
Deletes an employment branch by ID.
import { empleoService } from '$lib/server/services/empleo.service.js';
await empleoService.deleteSucursal(3);
Deleting a branch does not delete associated job applications. Consider deactivating instead.
Implementation Details
Source: src/lib/server/services/empleo.service.js:25-27
Usage Examples
// src/routes/empleo/+page.server.js
import { empleoService } from '$lib/server/services/empleo.service.js';
export async function load() {
const sucursales = await empleoService.getActive();
return { sucursales };
}
<!-- src/routes/empleo/+page.svelte -->
<script>
export let data;
</script>
<form method="POST" action="?/apply">
<h1>Trabaja con Nosotros</h1>
<div>
<label for="sucursal">Sucursal de Interés *</label>
<select id="sucursal" name="sucursal" required>
<option value="">Selecciona una sucursal</option>
{#each data.sucursales as sucursal}
<option value="{sucursal.nombre}">{sucursal.nombre}</option>
{/each}
</select>
</div>
<!-- Other form fields -->
<button type="submit">Enviar Postulación</button>
</form>
Admin Branch Management
// src/routes/admin/empleo/+page.server.js
import { empleoService } from '$lib/server/services/empleo.service.js';
import { fail } from '@sveltejs/kit';
export async function load() {
const sucursales = await empleoService.getAll();
return { sucursales };
}
export const actions = {
add: async ({ request }) => {
const formData = await request.formData();
const nombre = formData.get('nombre');
if (!nombre || nombre.trim().length < 2) {
return fail(400, { error: 'Nombre debe tener al menos 2 caracteres' });
}
try {
await empleoService.addSucursal(nombre.trim());
return { success: true };
} catch (error) {
console.error('Error adding branch:', error);
return fail(500, { error: 'Error al agregar sucursal' });
}
},
toggle: async ({ request }) => {
const formData = await request.formData();
const id = parseInt(formData.get('id'));
const activa = formData.get('activa') === 'true';
try {
await empleoService.toggleSucursal(id, activa);
return { success: true };
} catch (error) {
console.error('Error toggling branch:', error);
return fail(500, { error: 'Error al actualizar sucursal' });
}
},
delete: async ({ request }) => {
const formData = await request.formData();
const id = parseInt(formData.get('id'));
try {
await empleoService.deleteSucursal(id);
return { success: true };
} catch (error) {
console.error('Error deleting branch:', error);
return fail(500, { error: 'Error al eliminar sucursal' });
}
}
};
<!-- src/routes/admin/empleo/+page.svelte -->
<script>
import { enhance } from '$app/forms';
export let data;
$: activeCount = data.sucursales.filter(s => s.activa).length;
$: inactiveCount = data.sucursales.length - activeCount;
</script>
<div class="admin-empleo">
<header>
<h1>Sucursales de Empleo</h1>
<div class="stats">
<span class="badge active">{activeCount} activas</span>
<span class="badge inactive">{inactiveCount} inactivas</span>
</div>
</header>
<!-- Add new branch form -->
<form method="POST" action="?/add" class="add-form" use:enhance>
<input
type="text"
name="nombre"
placeholder="Nombre de nueva sucursal"
required
/>
<button type="submit">Agregar</button>
</form>
<!-- Branch list -->
<div class="sucursales-list">
{#each data.sucursales as sucursal (sucursal.id)}
<div class="sucursal-item" class:inactive={!sucursal.activa}>
<div class="info">
<h3>{sucursal.nombre}</h3>
<span class="status">
{sucursal.activa ? 'Activa' : 'Inactiva'}
</span>
</div>
<div class="actions">
<form method="POST" action="?/toggle" use:enhance>
<input type="hidden" name="id" value="{sucursal.id}" />
<input type="hidden" name="activa" value="{!sucursal.activa}" />
<button type="submit" class="toggle">
{sucursal.activa ? 'Desactivar' : 'Activar'}
</button>
</form>
<form method="POST" action="?/delete" use:enhance>
<input type="hidden" name="id" value="{sucursal.id}" />
<button type="submit" class="delete">Eliminar</button>
</form>
</div>
</div>
{/each}
{#if data.sucursales.length === 0}
<p class="empty">No hay sucursales configuradas</p>
{/if}
</div>
</div>
Integration with Postulaciones Service
// src/routes/empleo/+page.server.js
import { empleoService } from '$lib/server/services/empleo.service.js';
import { postulacionesService } from '$lib/server/services/postulaciones.service.js';
import { fail } from '@sveltejs/kit';
export async function load() {
const sucursales = await empleoService.getActive();
return { sucursales };
}
export const actions = {
apply: async ({ request }) => {
const formData = await request.formData();
const nombre = formData.get('nombre');
const telefono = formData.get('telefono');
const email = formData.get('email');
const sucursal = formData.get('sucursal');
const mensaje = formData.get('mensaje');
const cvFile = formData.get('cv');
// Validate sucursal exists and is active
const activeSucursales = await empleoService.getActive();
const isValidSucursal = activeSucursales.some(s => s.nombre === sucursal);
if (!isValidSucursal) {
return fail(400, { error: 'Sucursal inválida o no disponible' });
}
// Create application
try {
await postulacionesService.create(
{ nombre, telefono, email, sucursal, mensaje },
cvFile
);
return { success: true };
} catch (error) {
console.error('Error creating application:', error);
return fail(500, { error: 'Error al enviar postulación' });
}
}
};
Data Structure
Sucursal Object
interface EmpleoSucursal {
id: number;
nombre: string;
activa: boolean;
}
Validation Helpers
function validateSucursalName(nombre) {
const errors = [];
if (!nombre || nombre.trim().length < 2) {
errors.push('Nombre debe tener al menos 2 caracteres');
}
if (nombre.length > 100) {
errors.push('Nombre no puede exceder 100 caracteres');
}
// Check for special characters
const validPattern = /^[a-zA-ZáéíóúÁÉÍÓÚñÑ\s-]+$/;
if (!validPattern.test(nombre)) {
errors.push('Nombre contiene caracteres inválidos');
}
return errors;
}
// Usage
const errors = validateSucursalName(nombre);
if (errors.length > 0) {
return fail(400, { errors });
}
Check for Duplicate Names
import { empleoService } from '$lib/server/services/empleo.service.js';
async function isDuplicateSucursal(nombre) {
const sucursales = await empleoService.getAll();
return sucursales.some(s =>
s.nombre.toLowerCase().trim() === nombre.toLowerCase().trim()
);
}
// Usage in action
export const actions = {
add: async ({ request }) => {
const formData = await request.formData();
const nombre = formData.get('nombre').trim();
if (await isDuplicateSucursal(nombre)) {
return fail(400, { error: 'Ya existe una sucursal con ese nombre' });
}
await empleoService.addSucursal(nombre);
return { success: true };
}
};
Statistics and Analytics
import { empleoService } from '$lib/server/services/empleo.service.js';
import { postulacionesService } from '$lib/server/services/postulaciones.service.js';
async function getSucursalesStats() {
const [sucursales, postulaciones] = await Promise.all([
empleoService.getAll(),
postulacionesService.getAll()
]);
const stats = sucursales.map(sucursal => {
const applications = postulaciones.filter(p =>
p.sucursal === sucursal.nombre
);
return {
id: sucursal.id,
nombre: sucursal.nombre,
activa: sucursal.activa,
totalApplications: applications.length,
withCV: applications.filter(p => p.cvUrl).length,
recentApplications: applications.filter(p => {
const daysSince = (Date.now() - new Date(p.createdAt)) / (1000 * 60 * 60 * 24);
return daysSince <= 30;
}).length
};
});
return stats;
}
Usage in Admin Dashboard
// src/routes/admin/empleo/stats/+page.server.js
import { empleoService } from '$lib/server/services/empleo.service.js';
import { postulacionesService } from '$lib/server/services/postulaciones.service.js';
export async function load() {
const [sucursales, postulaciones] = await Promise.all([
empleoService.getAll(),
postulacionesService.getAll()
]);
const stats = sucursales.map(sucursal => ({
...sucursal,
applications: postulaciones.filter(p => p.sucursal === sucursal.nombre).length
}));
return { stats };
}
<!-- Stats display -->
<script>
export let data;
$: sortedStats = [...data.stats].sort((a, b) =>
b.applications - a.applications
);
</script>
<div class="stats-grid">
{#each sortedStats as stat}
<div class="stat-card">
<h3>{stat.nombre}</h3>
<div class="metric">
<span class="value">{stat.applications}</span>
<span class="label">postulaciones</span>
</div>
<div class="status" class:active={stat.activa}>
{stat.activa ? 'Activa' : 'Inactiva'}
</div>
</div>
{/each}
</div>
Best Practices
Use descriptive branch names that match physical locations (e.g., “Asunción Centro” instead of just “Asunción”).
Always validate that selected branches are active before creating job applications.
Consider deactivating branches instead of deleting them to preserve historical data.
Keep branch names consistent with your postulaciones data for accurate reporting.
Bulk Operations
import { empleoService } from '$lib/server/services/empleo.service.js';
/**
* Activate multiple branches at once
*/
async function activateMultipleSucursales(ids) {
const promises = ids.map(id =>
empleoService.toggleSucursal(id, true)
);
await Promise.all(promises);
}
/**
* Deactivate all branches except specified ones
*/
async function deactivateExcept(exceptIds) {
const sucursales = await empleoService.getAll();
const toDeactivate = sucursales
.filter(s => !exceptIds.includes(s.id) && s.activa)
.map(s => empleoService.toggleSucursal(s.id, false));
await Promise.all(toDeactivate);
}
/**
* Seed initial branches
*/
async function seedSucursales() {
const branches = [
'Asunción Centro',
'Ciudad del Este',
'Encarnación',
'Pedro Juan Caballero',
'Concepción'
];
for (const nombre of branches) {
await empleoService.addSucursal(nombre);
}
console.log(`Created ${branches.length} branches`);
}
Database Schema
CREATE TABLE empleo_sucursales (
id INTEGER PRIMARY KEY AUTOINCREMENT,
nombre TEXT NOT NULL UNIQUE,
activa BOOLEAN DEFAULT FALSE
);
CREATE INDEX idx_empleo_nombre ON empleo_sucursales(nombre);
CREATE INDEX idx_empleo_activa ON empleo_sucursales(activa);
Migration Example
// migrate-empleo-data.js
import { empleoService } from '$lib/server/services/empleo.service.js';
import { postulacionesService } from '$lib/server/services/postulaciones.service.js';
/**
* Migrate existing postulaciones to use standardized branch names
*/
async function migrateSucursalNames() {
const postulaciones = await postulacionesService.getAll();
// Map old names to new names
const mapping = {
'asuncion': 'Asunción Centro',
'cde': 'Ciudad del Este',
'encarnacion': 'Encarnación'
};
for (const postulacion of postulaciones) {
const oldName = postulacion.sucursal.toLowerCase();
const newName = mapping[oldName];
if (newName && newName !== postulacion.sucursal) {
// Update postulacion with new name
console.log(`Updating ${postulacion.id}: ${oldName} → ${newName}`);
// Implementation depends on your update logic
}
}
}