feat: soporte de contraseña REMOVERVOTOE3 para remover votos individuales y vaciar pruebas
This commit is contained in:
@@ -40,8 +40,9 @@ export const AdminPanel: React.FC<AdminPanelProps> = ({
|
||||
return;
|
||||
}
|
||||
|
||||
if (password.trim() !== 'RESETEOE3') {
|
||||
setAdminMsg({ text: 'Contraseña de administración incorrecta.', type: 'error' });
|
||||
const trimmedPass = password.trim();
|
||||
if (trimmedPass !== 'REMOVERVOTOE3' && trimmedPass !== 'RESETEOE3') {
|
||||
setAdminMsg({ text: 'Contraseña incorrecta. Se requiere "REMOVERVOTOE3".', type: 'error' });
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -49,17 +50,50 @@ export const AdminPanel: React.FC<AdminPanelProps> = ({
|
||||
setAdminMsg(null);
|
||||
|
||||
try {
|
||||
await ApiService.deleteVote(selectedVoter, password.trim());
|
||||
await ApiService.deleteVote(selectedVoter, trimmedPass);
|
||||
await onVoteResetSuccess();
|
||||
setAdminMsg({
|
||||
text: `Voto de "${selectedVoter}" restablecido y eliminado de PostgreSQL.`,
|
||||
text: `Voto de "${selectedVoter}" removido exitosamente del sistema.`,
|
||||
type: 'success',
|
||||
});
|
||||
setSelectedVoter('');
|
||||
setPassword('');
|
||||
} catch (err) {
|
||||
setAdminMsg({
|
||||
text: `Error al restablecer voto: ${err instanceof Error ? err.message : 'Error desconocido'}`,
|
||||
text: `Error al remover voto: ${err instanceof Error ? err.message : 'Error desconocido'}`,
|
||||
type: 'error',
|
||||
});
|
||||
} finally {
|
||||
setIsDeleting(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleResetAllVotes = async () => {
|
||||
const trimmedPass = password.trim();
|
||||
if (trimmedPass !== 'REMOVERVOTOE3' && trimmedPass !== 'RESETEOE3') {
|
||||
setAdminMsg({ text: 'Ingresa la contraseña "REMOVERVOTOE3" para vaciar todos los votos.', type: 'error' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!window.confirm('¿Confirmas que deseas eliminar TODOS los votos para reiniciar pruebas?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
setIsDeleting(true);
|
||||
setAdminMsg(null);
|
||||
|
||||
try {
|
||||
const res = await ApiService.deleteAllVotes(trimmedPass);
|
||||
await onVoteResetSuccess();
|
||||
setAdminMsg({
|
||||
text: `Todos los votos han sido removidos (${res.count} votos eliminados).`,
|
||||
type: 'success',
|
||||
});
|
||||
setSelectedVoter('');
|
||||
setPassword('');
|
||||
} catch (err) {
|
||||
setAdminMsg({
|
||||
text: `Error al vaciar votos: ${err instanceof Error ? err.message : 'Error desconocido'}`,
|
||||
type: 'error',
|
||||
});
|
||||
} finally {
|
||||
@@ -339,41 +373,62 @@ const VOTOS_EMBEBIDOS = ${JSON.stringify(rawVotes, null, 2)};`;
|
||||
|
||||
{/* Reset section */}
|
||||
<div className="space-y-3 pb-5 border-b border-white/5">
|
||||
<label className="block text-xs uppercase tracking-wider text-gray-400 font-semibold">
|
||||
Restablecer Voto por Error Humano (DELETE)
|
||||
</label>
|
||||
<div className="flex items-center justify-between">
|
||||
<label className="block text-xs uppercase tracking-wider text-gray-400 font-semibold">
|
||||
Remover Voto Individual o de Pruebas (DELETE)
|
||||
</label>
|
||||
<span className="text-[10px] font-mono text-amber-400/80 bg-amber-400/10 px-2 py-0.5 rounded">
|
||||
Clave requerida: REMOVERVOTOE3
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 sm:grid-cols-3 gap-3">
|
||||
<select
|
||||
value={selectedVoter}
|
||||
onChange={(e) => setSelectedVoter(e.target.value)}
|
||||
className="w-full px-3 py-2.5 bg-white/[0.04] border border-white/10 rounded-xl text-white text-xs focus:border-[#00B7D4] focus:outline-none uppercase font-mono"
|
||||
>
|
||||
<option value="">Selecciona votante...</option>
|
||||
{registeredVoters.map((v) => (
|
||||
<option key={v} value={v}>
|
||||
{v} → {rawVotes[v]}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-12 gap-3">
|
||||
<div className="sm:col-span-5">
|
||||
<select
|
||||
value={selectedVoter}
|
||||
onChange={(e) => setSelectedVoter(e.target.value)}
|
||||
className="w-full px-3 py-2.5 bg-white/[0.04] border border-white/10 rounded-xl text-white text-xs focus:border-[#00B7D4] focus:outline-none uppercase font-mono"
|
||||
>
|
||||
<option value="">Selecciona votante a remover...</option>
|
||||
{registeredVoters.map((v) => (
|
||||
<option key={v} value={v}>
|
||||
{v} → {rawVotes[v]}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<input
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
placeholder="Contraseña: RESETEOE3"
|
||||
className="w-full px-3 py-2.5 bg-white/[0.04] border border-white/10 rounded-xl text-white text-xs focus:border-[#00B7D4] focus:outline-none"
|
||||
/>
|
||||
<div className="sm:col-span-4">
|
||||
<input
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
placeholder="Clave: REMOVERVOTOE3"
|
||||
className="w-full px-3 py-2.5 bg-white/[0.04] border border-white/10 rounded-xl text-white text-xs focus:border-[#00B7D4] focus:outline-none"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleResetVote}
|
||||
disabled={isDeleting || !selectedVoter}
|
||||
className="px-4 py-2.5 bg-red-950/30 hover:bg-red-900/50 border border-red-500/40 text-red-300 rounded-xl text-xs font-bold uppercase tracking-wider transition-colors cursor-pointer disabled:opacity-30 flex items-center justify-center gap-2"
|
||||
>
|
||||
<Trash2 className="w-3.5 h-3.5" />
|
||||
<span>{isDeleting ? 'Borrando...' : 'Restablecer'}</span>
|
||||
</button>
|
||||
<div className="sm:col-span-3 flex gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleResetVote}
|
||||
disabled={isDeleting || !selectedVoter}
|
||||
className="flex-1 px-3 py-2.5 bg-red-950/40 hover:bg-red-900/60 border border-red-500/40 text-red-300 rounded-xl text-xs font-bold uppercase tracking-wider transition-colors cursor-pointer disabled:opacity-30 flex items-center justify-center gap-1.5"
|
||||
>
|
||||
<Trash2 className="w-3.5 h-3.5" />
|
||||
<span>{isDeleting ? '...' : 'Remover'}</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleResetAllVotes}
|
||||
disabled={isDeleting || registeredVoters.length === 0}
|
||||
title="Vaciar todos los votos registrados para pruebas"
|
||||
className="px-3 py-2.5 bg-amber-950/40 hover:bg-amber-900/60 border border-amber-500/40 text-amber-300 rounded-xl text-xs font-bold uppercase tracking-wider transition-colors cursor-pointer disabled:opacity-30 flex items-center justify-center gap-1"
|
||||
>
|
||||
<span>Vaciar Todo</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{adminMsg && (
|
||||
|
||||
Reference in New Issue
Block a user