feat: gestion de mantenimiento/penalizaciones y documentacion doc/

This commit is contained in:
2026-07-29 20:29:23 -06:00
parent 96fee5ccd3
commit 47d53f1528
5 changed files with 199 additions and 112 deletions
+151 -49
View File
@@ -1021,11 +1021,7 @@
if (p.estatus === 'Completado' && p.trimestreCompletado === trimestreActual) {
completedContainer.append(crearTarjetaProyecto(p, true));
} else if (p.estatus === 'En proceso') {
// Solo se muestra en el resumen si no ha pagado mantenimiento en este trimestre
console.log("[ResumenUI] Proyecto:", p.nombreProyecto, "estatus:", p.estatus, "mantoPagadoEnTrimestre:", p.mantenimientoPagadoEnTrimestre, "trimestreActual:", trimestreActual);
if (p.mantenimientoPagadoEnTrimestre !== trimestreActual) {
activeContainer.append(crearTarjetaProyecto(p, false));
}
activeContainer.append(crearTarjetaProyecto(p, false));
}
});
@@ -1073,21 +1069,32 @@
const completadoClass = esCompletado ? 'completado' : '';
const quincena = parseInt($('#etQuincena').val()) || 1;
const trimestreActual = Math.floor((quincena - 1) / 6) + 1;
const esFinTrimestre = (quincena % 6 === 0);
const yaResolvioManto = (proyecto.mantenimiento !== 'N/A' && proyecto.mantenimientoPagadoEnTrimestre === trimestreActual);
let actionButtonsHtml = '';
if (!esCompletado && esFinTrimestre) {
if (!esCompletado && esFinTrimestre && proyecto.dificultad !== 'Incidencia' && proyecto.mantenimiento !== 'N/A') {
const showPay = proyecto.mantenimiento !== 'N/A';
const buttonManto = showPay ?
`<button class="nav-button btn-pagar-mantenimiento" data-id="${proyecto.id}" style="background-color: #28a745; border: none; font-size: 11px; padding: 5px 8px; cursor: pointer; flex: 1;">Pagar Manto</button>` : '';
const penalizacionFormateada = formatCurrency(proyecto.penalizacion || 0);
const buttonDesechar = `<button class="nav-button btn-desechar-proyecto" data-id="${proyecto.id}" style="background-color: #dc3545; border: none; font-size: 11px; padding: 5px 8px; cursor: pointer; flex: 1;">Desechar (${penalizacionFormateada})</button>`;
let buttonManto = '';
let buttonPenalizar = '';
if (yaResolvioManto) {
buttonManto = showPay ?
`<button disabled class="nav-button btn-pagar-mantenimiento" data-id="${proyecto.id}" style="background-color: #6c757d; border: none; font-size: 11px; padding: 5px 8px; cursor: not-allowed; flex: 1;">Pagar Manto</button>` : '';
buttonPenalizar = `<button disabled class="nav-button btn-penalizar-proyecto" data-id="${proyecto.id}" style="background-color: #6c757d; border: none; font-size: 11px; padding: 5px 8px; cursor: not-allowed; flex: 1;">Penalizar</button>`;
} else {
buttonManto = showPay ?
`<button class="nav-button btn-pagar-mantenimiento" data-id="${proyecto.id}" style="background-color: #28a745; border: none; font-size: 11px; padding: 5px 8px; cursor: pointer; flex: 1;">Pagar Manto</button>` : '';
buttonPenalizar = `<button class="nav-button btn-penalizar-proyecto" data-id="${proyecto.id}" style="background-color: #dc3545; border: none; font-size: 11px; padding: 5px 8px; cursor: pointer; flex: 1;">Penalizar</button>`;
}
actionButtonsHtml = `
<div class="project-actions" style="display: flex; gap: 8px; margin-top: 10px; width: 100%;">
${buttonManto}
${buttonDesechar}
${buttonPenalizar}
</div>
`;
}
@@ -1105,7 +1112,7 @@
}
return `
<div class="project-card ${completadoClass}" style="flex-basis: 48%; display: flex; flex-direction: column; justify-content: space-between; min-height: 180px; box-sizing: border-box; padding: 10px;">
<div class="project-card ${completadoClass}" style="flex-basis: 48%; display: flex; flex-direction: column; justify-content: space-between; min-height: 180px; box-sizing: border-box; padding: 10px; ${yaResolvioManto ? 'background-color: #e9e9e9; opacity: 0.75; border-color: #aaa;' : ''}">
<div>
<div class="project-header" style="font-weight: bold; margin-bottom: 5px; text-align: center; border-bottom: 1px solid gray; padding-bottom: 4px;">${proyecto.nombreProyecto.toUpperCase()}</div>
<div class="project-body" style="padding: 5px; text-align: center;">
@@ -1423,7 +1430,7 @@
guardaSesion();
}
}
} else if (accionQuincenal === 'Descartar un proyecto (se va al limbo con todo y activos)') {
} else if (accionQuincenal === 'Descartar un proyecto') {
const proyectosEnProceso = proyectos.filter(p => p.estatus === 'En proceso');
if (proyectosEnProceso.length === 0) {
alert('No hay proyectos en proceso para descartar.');
@@ -1433,11 +1440,16 @@
let dialogContent = $('<div><p>Selecciona el proyecto a descartar:</p></div>');
let projectList = $('<form>');
proyectosEnProceso.forEach((p) => {
let labelText = p.nombreProyecto;
if (p.dificultad === 'Incidencia') {
const shortDesc = p.descripcion.length > 35 ? p.descripcion.substring(0, 35) + '...' : p.descripcion;
labelText = `${p.nombreProyecto} (${shortDesc})`;
}
projectList.append(
`<div style="display: flex; align-items: flex-start; margin: 8px 0; text-align: left;">
<input type="radio" id="proj-discard-${p.id}" name="proyectoADescartar" value="${p.id}" style="margin-top: 2px; flex-shrink: 0;">
<label for="proj-discard-${p.id}" style="margin-left: 8px; cursor: pointer;">
${p.nombreProyecto} <span style="color: #555; font-size: 0.9em;">(${p.departamento})</span>
${labelText}
</label>
</div>`
);
@@ -1477,6 +1489,74 @@
$(this).dialog('destroy').remove();
},
});
} else if (accionQuincenal === 'Omitir un mantenimiento') {
const esFinTrimestre = (quincenaActual % 6 === 0);
const trimestreActual = Math.floor((quincenaActual - 1) / 6) + 1;
if (!esFinTrimestre) {
alert('Esta Habilidad Especial solo se puede utilizar en el cierre de un trimestre (quincenas 6, 12, 18 y 24).');
return;
}
const proyectosPendientes = proyectos.filter(p =>
p.estatus === 'En proceso' &&
p.mantenimiento !== 'N/A' &&
p.mantenimientoPagadoEnTrimestre !== trimestreActual
);
if (proyectosPendientes.length === 0) {
alert('No hay proyectos activos con mantenimiento pendiente en este trimestre.');
return;
}
let dialogContent = $('<div><p>Selecciona el proyecto al que deseas omitir el costo de mantenimiento:</p></div>');
let projectList = $('<form>');
proyectosPendientes.forEach((p) => {
projectList.append(
`<div style="display: flex; align-items: flex-start; margin: 8px 0; text-align: left;">
<input type="radio" id="proj-omit-${p.id}" name="proyectoAOmitir" value="${p.id}" style="margin-top: 2px; flex-shrink: 0;">
<label for="proj-omit-${p.id}" style="margin-left: 8px; cursor: pointer;">
${p.nombreProyecto} <span style="color: #555; font-size: 0.9em;">(Costo: ${p.mantenimiento})</span>
</label>
</div>`
);
});
dialogContent.append(projectList);
dialogContent.dialog({
modal: true,
title: 'Omitir Mantenimiento - Logística y Sistemas',
width: 450,
buttons: {
"Omitir Costo": function() {
const selectedProjectId = $('input[name="proyectoAOmitir"]:checked').val();
if (!selectedProjectId) {
alert('Por favor, selecciona un proyecto.');
return;
}
const proyectoAOmitir = proyectos.find(p => p.id == selectedProjectId);
if (proyectoAOmitir) {
proyectoAOmitir.mantenimientoPagadoEnTrimestre = trimestreActual;
setArrayValue(elArreglo, 'accionEspecialLastUsed', quincenaActual);
actualizarResumenUI();
actualizarTarjetaDepto('LogisticaYSistemas');
var audio = new Audio('./Microwave_bell.wav');
audio.play();
alert(`¡El mantenimiento del proyecto "${proyectoAOmitir.nombreProyecto}" ha sido omitido con éxito sin costo!`);
if (typeof guardaSesion === 'function') {
guardaSesion();
} else if (typeof guardarDatosEnServidor === 'function') {
guardarDatosEnServidor();
}
}
$(this).dialog('close');
},
"Cancelar": function() {
$(this).dialog('close');
}
},
close: function() {
$(this).dialog('destroy').remove();
}
});
} else if (accionQuincenal === 'Ordenar los próximos 5 proyectos') {
alert('Esta habilidad especial solo se puede utilizar desde el tablero de juego.');
return;
@@ -1539,11 +1619,35 @@
const recT = parseInt(findArrayValue(deptoArr, 'recursoTiempo')) || 0;
const recP = parseInt(findArrayValue(deptoArr, 'recursoPersonal')) || 0;
const recC = parseInt(findArrayValue(deptoArr, 'recursoCapital')) || 0;
// Get department skills discounts
const habTiempo = findArrayValue(deptoArr, 'habilidadTiempo');
const habPersonal = findArrayValue(deptoArr, 'habilidadPersonal');
const habCapital = findArrayValue(deptoArr, 'habilidadCapital');
const discountT = (habTiempo && habTiempo !== 0) ? parseInt(findArrayValue(arreglosCapacitaciones[habTiempo], 'beneficio')) || 0 : 0;
const discountP = (habPersonal && habPersonal !== 0) ? parseInt(findArrayValue(arreglosCapacitaciones[habPersonal], 'beneficio')) || 0 : 0;
const discountC = (habCapital && habCapital !== 0) ? parseInt(findArrayValue(arreglosCapacitaciones[habCapital], 'beneficio')) || 0 : 0;
const finalCostT = Math.max(0, cost.t + discountT);
const finalCostP = Math.max(0, cost.p + discountP);
const finalCostC = Math.max(0, cost.c + discountC);
console.log("[PagarManto] Recursos depto actual:", {
recT,
recP,
recC
});
console.log("[PagarManto] Descuentos:", {
discountT,
discountP,
discountC
});
console.log("[PagarManto] Costo final con descuento:", {
finalCostT,
finalCostP,
finalCostC
});
// Verificar si LogisticaYSistemas tiene comprada y disponible su habilidad especial
let logisticaHabilidadEspecialDisponible = false;
@@ -1602,7 +1706,7 @@
},
"Pagar Normal (Con costo)": function() {
$(this).dialog('close');
ejecutarPagoNormal(proyecto, cost, deptoArr, recT, recP, recC, deptoNombre, trimestreActual, quincena);
ejecutarPagoNormal(proyecto, deptoArr, recT, recP, recC, deptoNombre, trimestreActual, finalCostT, finalCostP, finalCostC);
},
"Cancelar": function() {
$(this).dialog('close');
@@ -1610,26 +1714,28 @@
}
});
} else {
ejecutarPagoNormal(proyecto, cost, deptoArr, recT, recP, recC, deptoNombre, trimestreActual, quincena);
ejecutarPagoNormal(proyecto, deptoArr, recT, recP, recC, deptoNombre, trimestreActual, finalCostT, finalCostP, finalCostC);
}
}
function ejecutarPagoNormal(proyecto, cost, deptoArr, recT, recP, recC, deptoNombre, trimestreActual, quincena) {
if (recT < cost.t || recP < cost.p || recC < cost.c) {
function ejecutarPagoNormal(proyecto, deptoArr, recT, recP, recC, deptoNombre, trimestreActual, finalCostT, finalCostP, finalCostC) {
if (recT < finalCostT || recP < finalCostP || recC < finalCostC) {
alert(`El departamento seleccionado (${deptoNombre}) NO tiene suficientes recursos para pagar el mantenimiento del proyecto "${proyecto.nombreProyecto}".\n` +
`Costo requerido: Tiempo: ${cost.t}, Personal: ${cost.p}, Capital: ${cost.c}\n` +
`Costo requerido (con descuento): ${finalCostT}T ${finalCostP}P ${finalCostC}C\n` +
`Recursos actuales: Tiempo: ${recT}, Personal: ${recP}, Capital: ${recC}`);
return;
}
const confirmMessage = `¿Estás seguro de que deseas pagar el mantenimiento de <b>${proyecto.mantenimiento}</b> para el proyecto "${proyecto.nombreProyecto}" desde el departamento <b>${deptoNombre}</b>?`;
const confirmMessage = `¿Estás seguro de que deseas pagar el mantenimiento para el proyecto "${proyecto.nombreProyecto}" desde el departamento <b>${deptoNombre}</b>?<br>` +
`Costo original: <b>${proyecto.mantenimiento}</b><br>` +
`Costo final con descuento aplicado: <b>${finalCostT}T ${finalCostP}P ${finalCostC}C</b>`;
confirm(confirmMessage, function(confirmed) {
if (confirmed) {
// Deduct resources
setArrayValue(deptoArr, 'recursoTiempo', recT - cost.t);
setArrayValue(deptoArr, 'recursoPersonal', recP - cost.p);
setArrayValue(deptoArr, 'recursoCapital', recC - cost.c);
setArrayValue(deptoArr, 'recursoTiempo', recT - finalCostT);
setArrayValue(deptoArr, 'recursoPersonal', recP - finalCostP);
setArrayValue(deptoArr, 'recursoCapital', recC - finalCostC);
// Mark project maintenance as paid in the current trimester
proyecto.mantenimientoPagadoEnTrimestre = trimestreActual;
@@ -1655,7 +1761,7 @@
}, 450);
}
function desecharProyecto(projectId) {
function penalizarProyecto(projectId) {
const proyecto = proyectos.find(p => p.id == projectId);
if (!proyecto) {
alert('No se encontró el proyecto.');
@@ -1686,41 +1792,40 @@
}
if (operacionesHabilidadEspecialDisponible) {
const dialogMsg = `El departamento de Operaciones tiene disponible la Habilidad Especial para descartar un proyecto sin costo.<br><br>` +
`¿Deseas usar la Habilidad Especial para desechar el proyecto "${proyecto.nombreProyecto}" <b>sin pagar la penalización</b>,<br>` +
`o prefieres realizar un desecho normal pagando la penalización de <b>${penalizacionFormateada}</b>?`;
const dialogMsg = `El departamento de Operaciones tiene disponible la Habilidad Especial para exentar de penalización a un proyecto.<br><br>` +
`¿Deseas usar la Habilidad Especial para el proyecto "${proyecto.nombreProyecto}" <b>sin pagar la penalización</b>,<br>` +
`o prefieres pagar la penalización de <b>${penalizacionFormateada}</b> de manera normal?`;
$('<div></div>')
.html(dialogMsg)
.dialog({
modal: true,
title: 'Desechar Proyecto - Operaciones',
title: 'Penalizar Proyecto - Operaciones',
width: 480,
buttons: {
"Usar Habilidad (Sin costo)": function() {
$(this).dialog('close');
// Mark project as discarded
proyecto.estatus = 'descartado';
// Mantiene el estatus 'En proceso' pero marca el mantenimiento resuelto en este trimestre
proyecto.mantenimientoPagadoEnTrimestre = trimestreActual;
// Update skill cooldown
// Cooldown de la habilidad
setArrayValue(elArregloOperaciones, 'accionEspecialLastUsed', quincena);
// Save and update UI
actualizarResumenUI();
actualizarTarjetaDepto('Operaciones'); // Refresca la tarjeta del depto en la UI
actualizarTarjetaDepto('Operaciones');
var audio = new Audio('./Microwave_bell.wav');
audio.play();
alert(`El proyecto "${proyecto.nombreProyecto}" ha sido desechado sin costo utilizando la Habilidad Especial de Operaciones.`);
alert(`El proyecto "${proyecto.nombreProyecto}" ha sido exentado de la penalización utilizando la Habilidad Especial de Operaciones.`);
if (typeof guardarDatosEnServidor === 'function') {
guardarDatosEnServidor();
}
},
"Desechar Normal (Con costo)": function() {
"Pagar Penalización (Con costo)": function() {
$(this).dialog('close');
ejecutarDesechoNormal(proyecto, penalizacionVal, penalizacionFormateada, trimestreActual);
ejecutarPenalizacionNormal(proyecto, penalizacionVal, penalizacionFormateada, trimestreActual);
},
"Cancelar": function() {
$(this).dialog('close');
@@ -1728,46 +1833,43 @@
}
});
} else {
ejecutarDesechoNormal(proyecto, penalizacionVal, penalizacionFormateada, trimestreActual);
ejecutarPenalizacionNormal(proyecto, penalizacionVal, penalizacionFormateada, trimestreActual);
}
}
function ejecutarDesechoNormal(proyecto, penalizacionVal, penalizacionFormateada, trimestreActual) {
// Validar que el acumulado general acumulado hasta el trimestre actual sea suficiente
function ejecutarPenalizacionNormal(proyecto, penalizacionVal, penalizacionFormateada, trimestreActual) {
let acumuladoDisponible = 0;
for (let t = 1; t <= trimestreActual; t++) {
acumuladoDisponible += (arregloGeneral['trimestre' + t] || 0);
}
if (acumuladoDisponible < penalizacionVal) {
alert(`No puedes desechar el proyecto "${proyecto.nombreProyecto}" porque no hay acumulado general suficiente.\n` +
alert(`No puedes penalizar el proyecto "${proyecto.nombreProyecto}" porque no hay acumulado general suficiente.\n` +
`Penalización requerida: ${penalizacionFormateada}\n` +
`Acumulado general disponible: ${formatCurrency(acumuladoDisponible)}`);
return;
}
const confirmMessage = `¿Estás seguro de que deseas desechar el proyecto "${proyecto.nombreProyecto}"?<br>` +
const confirmMessage = `¿Estás seguro de que quieres pagar la penalización del proyecto "${proyecto.nombreProyecto}"?<br>` +
`Se aplicará una penalización de <b>${penalizacionFormateada}</b> del acumulado general.`;
confirm(confirmMessage, function(confirmed) {
if (confirmed) {
// Subtract from current trimester's earnings
const trimestreKey = 'trimestre' + trimestreActual;
if (typeof arregloGeneral[trimestreKey] === 'undefined') {
arregloGeneral[trimestreKey] = 0;
}
arregloGeneral[trimestreKey] -= penalizacionVal;
// Mark project as discarded
proyecto.estatus = 'descartado';
// Mantiene el estatus 'En proceso' pero marca el mantenimiento resuelto en este trimestre
proyecto.mantenimientoPagadoEnTrimestre = trimestreActual;
// Save and update UI
actualizarResumenUI();
var audio = new Audio('./Microwave_bell.wav');
audio.play();
alert(`El proyecto "${proyecto.nombreProyecto}" ha sido desechado. Se aplicó una penalización de ${penalizacionFormateada}.`);
alert(`El proyecto "${proyecto.nombreProyecto}" ha sido penalizado. Se aplicó una penalización de ${penalizacionFormateada}.`);
if (typeof guardarDatosEnServidor === 'function') {
guardarDatosEnServidor();
@@ -1776,15 +1878,15 @@
}, 450);
}
// Delegación de eventos para los botones de pagar y desechar
// Delegación de eventos para los botones de pagar y penalizar
$(document).on('click', '.btn-pagar-mantenimiento', function() {
const id = $(this).data('id');
pagarMantenimientoProyecto(id);
});
$(document).on('click', '.btn-desechar-proyecto', function() {
$(document).on('click', '.btn-penalizar-proyecto', function() {
const id = $(this).data('id');
desecharProyecto(id);
penalizarProyecto(id);
});
// El listener para el click en .departamentos ya está en jsCode.js y funcionará ahora