The Sugerencias Service manages customer suggestions and feedback submissions, including configurable suggestion types and read status tracking.
Overview
This service handles the complete lifecycle of customer suggestions: creation, retrieval, marking as read, and deletion. It also manages the configuration of suggestion types displayed in the dropdown.
Dependencies
sugerenciasRepository - Database operations for suggestions
Methods
getAll()
Retrieves all customer suggestions.
import { sugerenciasService } from '$lib/server/services/sugerencias.service.js';
const sugerencias = await sugerenciasService.getAll();
Array of suggestion objects:
id: Suggestion identifier
tipo: Type/category of suggestion
nombre: Customer name
mensaje: Suggestion message/content
leida: Boolean indicating if read
createdAt: Timestamp of creation
Example Response
[
{
id: 1,
tipo: 'Mejora del Servicio',
nombre: 'Juan Pérez',
mensaje: 'Sugerencia para mejorar la atención...',
leida: false,
createdAt: '2024-03-12T10:30:00Z'
},
{
id: 2,
tipo: 'Producto Nuevo',
nombre: 'María González',
mensaje: 'Podrían agregar cemento especial...',
leida: true,
createdAt: '2024-03-11T15:20:00Z'
}
]
Implementation Details
Source: src/lib/server/services/sugerencias.service.js:5-7
create()
Creates a new customer suggestion.
import { sugerenciasService } from '$lib/server/services/sugerencias.service.js';
await sugerenciasService.create({
tipo: 'Mejora del Servicio',
nombre: 'Carlos Rodríguez',
mensaje: 'Sería genial si pudieran ofrecer entrega los sábados'
});
Type or category of suggestion (from configured options)
Suggestion message or feedback content
New suggestions are created with leida: false by default.
Implementation Details
Source: src/lib/server/services/sugerencias.service.js:10-12
markAsRead()
Marks a suggestion as read.
import { sugerenciasService } from '$lib/server/services/sugerencias.service.js';
await sugerenciasService.markAsRead(1);
Suggestion ID to mark as read
This sets leida: true for the specified suggestion.
Implementation Details
Source: src/lib/server/services/sugerencias.service.js:15-17
delete()
Deletes a suggestion by ID.
import { sugerenciasService } from '$lib/server/services/sugerencias.service.js';
await sugerenciasService.delete(1);
Implementation Details
Source: src/lib/server/services/sugerencias.service.js:20-22
getConfig()
Retrieves the configuration for suggestion types.
import { sugerenciasService } from '$lib/server/services/sugerencias.service.js';
const config = await sugerenciasService.getConfig();
Configuration object containing:
opciones: Array of suggestion type strings
Example Response
{
opciones: [
'Mejora del Servicio',
'Producto Nuevo',
'Queja',
'Felicitación',
'Otro'
]
}
Implementation Details
Source: src/lib/server/services/sugerencias.service.js:25-27
updateConfig()
Updates the suggestion type options with JSON parsing.
import { sugerenciasService } from '$lib/server/services/sugerencias.service.js';
await sugerenciasService.updateConfig([
'Mejora del Servicio',
'Producto Nuevo',
'Sucursal Nueva',
'Queja',
'Felicitación'
]);
// Or with JSON string
await sugerenciasService.updateConfig(
JSON.stringify(['Opción 1', 'Opción 2'])
);
Array of suggestion type strings or JSON string
JSON Parsing
The service automatically handles both formats:
const parsed = typeof opciones === 'string' ? JSON.parse(opciones) : opciones;
Implementation Details
Source: src/lib/server/services/sugerencias.service.js:30-33
Usage Examples
// src/routes/sugerencias/+page.server.js
import { sugerenciasService } from '$lib/server/services/sugerencias.service.js';
import { fail } from '@sveltejs/kit';
export async function load() {
const config = await sugerenciasService.getConfig();
return { tiposSugerencia: config.opciones };
}
export const actions = {
submit: async ({ request }) => {
const formData = await request.formData();
const tipo = formData.get('tipo');
const nombre = formData.get('nombre');
const mensaje = formData.get('mensaje');
// Validation
if (!tipo || !nombre || !mensaje) {
return fail(400, { error: 'Todos los campos son requeridos' });
}
if (mensaje.length < 10) {
return fail(400, { error: 'El mensaje debe tener al menos 10 caracteres' });
}
try {
await sugerenciasService.create({ tipo, nombre, mensaje });
return { success: true };
} catch (error) {
return fail(500, { error: 'Error al enviar sugerencia' });
}
}
};
<!-- src/routes/sugerencias/+page.svelte -->
<script>
import { enhance } from '$app/forms';
export let data;
export let form;
</script>
<form method="POST" action="?/submit" use:enhance>
<h1>Envíanos tu Sugerencia</h1>
<div>
<label for="nombre">Tu Nombre</label>
<input
type="text"
id="nombre"
name="nombre"
required
/>
</div>
<div>
<label for="tipo">Tipo de Sugerencia</label>
<select id="tipo" name="tipo" required>
<option value="">Selecciona un tipo</option>
{#each data.tiposSugerencia as tipo}
<option value="{tipo}">{tipo}</option>
{/each}
</select>
</div>
<div>
<label for="mensaje">Tu Mensaje</label>
<textarea
id="mensaje"
name="mensaje"
rows="6"
required
minlength="10"
></textarea>
</div>
<button type="submit">Enviar Sugerencia</button>
{#if form?.success}
<p class="success">¡Gracias! Tu sugerencia ha sido enviada.</p>
{/if}
{#if form?.error}
<p class="error">{form.error}</p>
{/if}
</form>
Admin Dashboard
// src/routes/admin/sugerencias/+page.server.js
import { sugerenciasService } from '$lib/server/services/sugerencias.service.js';
import { fail } from '@sveltejs/kit';
export async function load() {
const [sugerencias, config] = await Promise.all([
sugerenciasService.getAll(),
sugerenciasService.getConfig()
]);
return {
sugerencias,
tiposConfig: config.opciones
};
}
export const actions = {
markRead: async ({ request }) => {
const formData = await request.formData();
const id = parseInt(formData.get('id'));
try {
await sugerenciasService.markAsRead(id);
return { success: true };
} catch (error) {
return fail(500, { error: error.message });
}
},
delete: async ({ request }) => {
const formData = await request.formData();
const id = parseInt(formData.get('id'));
try {
await sugerenciasService.delete(id);
return { success: true };
} catch (error) {
return fail(500, { error: error.message });
}
},
updateConfig: async ({ request }) => {
const formData = await request.formData();
const opciones = formData.get('opciones'); // JSON string
try {
await sugerenciasService.updateConfig(opciones);
return { success: true };
} catch (error) {
return fail(500, { error: error.message });
}
}
};
<!-- src/routes/admin/sugerencias/+page.svelte -->
<script>
export let data;
$: unreadCount = data.sugerencias.filter(s => !s.leida).length;
</script>
<div class="admin-sugerencias">
<header>
<h1>Sugerencias de Clientes</h1>
<span class="badge">{unreadCount} sin leer</span>
</header>
<div class="sugerencias-list">
{#each data.sugerencias as sugerencia (sugerencia.id)}
<div class="sugerencia" class:unread={!sugerencia.leida}>
<div class="header">
<span class="tipo">{sugerencia.tipo}</span>
<span class="date">
{new Date(sugerencia.createdAt).toLocaleDateString()}
</span>
</div>
<h3>{sugerencia.nombre}</h3>
<p>{sugerencia.mensaje}</p>
<div class="actions">
{#if !sugerencia.leida}
<form method="POST" action="?/markRead">
<input type="hidden" name="id" value="{sugerencia.id}" />
<button type="submit">Marcar como Leída</button>
</form>
{/if}
<form method="POST" action="?/delete">
<input type="hidden" name="id" value="{sugerencia.id}" />
<button type="submit" class="delete">Eliminar</button>
</form>
</div>
</div>
{/each}
</div>
</div>
Data Structure
Sugerencia Object
interface Sugerencia {
id: number;
tipo: string;
nombre: string;
mensaje: string;
leida: boolean;
createdAt: string; // ISO timestamp
}
Config Object
interface SugerenciasConfig {
opciones: string[];
}
Default Suggestion Types
Common suggestion type options:
const defaultOptions = [
'Mejora del Servicio',
'Producto Nuevo',
'Queja',
'Felicitación',
'Sucursal Nueva',
'Horario de Atención',
'Otro'
];
await sugerenciasService.updateConfig(defaultOptions);
Filtering and Sorting
Get Unread Suggestions
import { sugerenciasService } from '$lib/server/services/sugerencias.service.js';
const allSugerencias = await sugerenciasService.getAll();
const unread = allSugerencias.filter(s => !s.leida);
const read = allSugerencias.filter(s => s.leida);
Sort by Date
const sorted = allSugerencias.sort((a, b) =>
new Date(b.createdAt) - new Date(a.createdAt)
);
Filter by Type
const quejas = allSugerencias.filter(s => s.tipo === 'Queja');
const mejoras = allSugerencias.filter(s => s.tipo === 'Mejora del Servicio');
Best Practices
Display unread count prominently in admin dashboard for quick visibility.
Validate and sanitize user input before creating suggestions to prevent XSS attacks.
Limit suggestion types to 5-7 options for better user experience.
Consider implementing email notifications for new suggestions.
Email Notification Example
import { sugerenciasService } from '$lib/server/services/sugerencias.service.js';
import { sendEmail } from '$lib/server/email';
async function createSugerenciaWithNotification(data) {
// Create suggestion
await sugerenciasService.create(data);
// Send notification email
await sendEmail({
to: 'admin@provesa.com',
subject: `Nueva Sugerencia: ${data.tipo}`,
html: `
<h2>Nueva Sugerencia Recibida</h2>
<p><strong>Tipo:</strong> ${data.tipo}</p>
<p><strong>Nombre:</strong> ${data.nombre}</p>
<p><strong>Mensaje:</strong></p>
<p>${data.mensaje}</p>
`
});
}
Statistics and Analytics
import { sugerenciasService } from '$lib/server/services/sugerencias.service.js';
async function getSugerenciasStats() {
const sugerencias = await sugerenciasService.getAll();
const stats = {
total: sugerencias.length,
unread: sugerencias.filter(s => !s.leida).length,
byType: {}
};
// Count by type
sugerencias.forEach(s => {
stats.byType[s.tipo] = (stats.byType[s.tipo] || 0) + 1;
});
// Most common type
stats.mostCommon = Object.entries(stats.byType)
.sort(([,a], [,b]) => b - a)[0]?.[0];
return stats;
}
Validation Helpers
function validateSugerencia(data) {
const errors = [];
if (!data.tipo || data.tipo.trim().length === 0) {
errors.push('Tipo es requerido');
}
if (!data.nombre || data.nombre.trim().length < 2) {
errors.push('Nombre debe tener al menos 2 caracteres');
}
if (!data.mensaje || data.mensaje.trim().length < 10) {
errors.push('Mensaje debe tener al menos 10 caracteres');
}
if (data.mensaje && data.mensaje.length > 1000) {
errors.push('Mensaje no puede exceder 1000 caracteres');
}
return errors;
}
// Usage
const errors = validateSugerencia(formData);
if (errors.length > 0) {
return fail(400, { errors });
}