* **Nuevo botón de Pantalla Completa:** Integrado en los encabezados de la pantalla de Resumen y el Tablero mediante la API de Fullscreen de HTML5, actualizando su texto de forma reactiva a eventos del navegador.

*   **Alineación de Departamentos:** Se fijó la altura (38px) de la celda de habilidad especial en el tablero y se restauró el tamaño de letra original (14px) con un line-height controlado (1.2) para evitar desfases de altura cuando el texto ocupa dos líneas sin perder legibilidad.
This commit is contained in:
2026-07-03 00:47:29 -06:00
parent d95694b919
commit 96fee5ccd3
14 changed files with 659 additions and 57 deletions
+354 -14
View File
@@ -256,7 +256,10 @@
<img id="logo" src="img/logoColaborando-tablero.png">
<div style="display: flex; justify-content: space-between; align-items: center; padding: 5px 10px;">
<div id="nombreSesion" style="font-weight: bold; font-size: 16px; color: #555;"></div>
<a href="colaborando2.php?n=<?php echo htmlspecialchars($_GET['n'] ?? $_GET['nombre'] ?? ''); ?>" class="nav-button">Ir al Tablero</a>
<div style="display: flex; gap: 5px;">
<button id="btnFullscreen" onclick="toggleFullscreen()" class="nav-button" style="border: none; cursor: pointer;">Pantalla Completa</button>
<a href="colaborando2.php?n=<?php echo htmlspecialchars($_GET['n'] ?? $_GET['nombre'] ?? ''); ?>" class="nav-button">Ir al Tablero</a>
</div>
</div>
</div>
<div class="card">
@@ -778,10 +781,14 @@
if (!deptoArr || !deptoCard.length) return;
// Actualizar recursos
deptoCard.find('#tiempoCh').text(findArrayValue(deptoArr, 'recursoTiempo'));
deptoCard.find('#personalCh').text(findArrayValue(deptoArr, 'recursoPersonal'));
deptoCard.find('#capitalCh').text(findArrayValue(deptoArr, 'recursoCapital'));
// Actualizar recursos (con selector flexible para soportar el deptoActual con IDs renombrados)
const tiempoElem = deptoCard.find('#tiempoCh').length ? deptoCard.find('#tiempoCh') : deptoCard.find('#recursoDispTiempoGde');
const personalElem = deptoCard.find('#personalCh').length ? deptoCard.find('#personalCh') : deptoCard.find('#recursoDispPersonalGde');
const capitalElem = deptoCard.find('#capitalCh').length ? deptoCard.find('#capitalCh') : deptoCard.find('#recursoDispCapitalGde');
tiempoElem.text(findArrayValue(deptoArr, 'recursoTiempo'));
personalElem.text(findArrayValue(deptoArr, 'recursoPersonal'));
capitalElem.text(findArrayValue(deptoArr, 'recursoCapital'));
// Actualizar activos
deptoCard.find('#activoCh1').text(findArrayValue(deptoArr, 'activo1'));
@@ -1014,7 +1021,11 @@
if (p.estatus === 'Completado' && p.trimestreCompletado === trimestreActual) {
completedContainer.append(crearTarjetaProyecto(p, true));
} else if (p.estatus === 'En proceso') {
activeContainer.append(crearTarjetaProyecto(p, false));
// 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));
}
}
});
@@ -1061,15 +1072,53 @@
const completadoClass = esCompletado ? 'completado' : '';
return `
<div class="project-card ${completadoClass}" style="flex-basis: 48%;">
<div class="project-header">${proyecto.nombreProyecto.toUpperCase()}</div>
<div class="project-body">
<i>${proyecto.descripcion}</i>
<div class="project-assets-req">${activosReqHtml}</div>
<div class="project-assets-asig">${activosAsigHtml}</div>
const quincena = parseInt($('#etQuincena').val()) || 1;
const esFinTrimestre = (quincena % 6 === 0);
let actionButtonsHtml = '';
if (!esCompletado && esFinTrimestre) {
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>`;
actionButtonsHtml = `
<div class="project-actions" style="display: flex; gap: 8px; margin-top: 10px; width: 100%;">
${buttonManto}
${buttonDesechar}
</div>
`;
}
let infoAdicionalHtml = '';
if (!esCompletado) {
const penalizacionFormateada = formatCurrency(proyecto.penalizacion || 0);
const potencialVal = proyecto.potencial || 'N/A';
infoAdicionalHtml = `
<div class="project-info-adicional" style="font-size: 1.2em; color: #444; margin-top: 8px; border-top: 1px dashed #ccc; padding-top: 5px; text-align: left; display: flex; justify-content: space-between;">
<span><b>Potencial:</b> ${potencialVal}</span>
<span><b>Penalización:</b> ${penalizacionFormateada}</span>
</div>
`;
}
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>
<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;">
<i style="font-size: 0.9em; color: #555;">${proyecto.descripcion}</i>
<div class="project-assets-req" style="margin-top: 5px; display: flex; justify-content: center; gap: 2px;">${activosReqHtml}</div>
<div class="project-assets-asig" style="margin-top: 5px; display: flex; justify-content: center; gap: 2px;">${activosAsigHtml}</div>
${infoAdicionalHtml}
</div>
</div>
<div>
<div class="project-footer" style="font-size: 0.9em; margin-top: 8px; font-weight: bold; border-top: 1px solid #ddd; padding-top: 5px; text-align: center;">MANTENIMIENTO: ${proyecto.mantenimiento}</div>
${actionButtonsHtml}
</div>
<div class="project-footer">MANTENIMIENTO: ${proyecto.mantenimiento}</div>
</div>
`;
}
@@ -1447,6 +1496,297 @@
}
});
function parseMantenimiento(mantenimientoStr) {
let t = 0,
p = 0,
c = 0;
if (mantenimientoStr && mantenimientoStr !== 'N/A') {
const matchesT = mantenimientoStr.match(/(\d+)T/i);
const matchesP = mantenimientoStr.match(/(\d+)P/i);
const matchesC = mantenimientoStr.match(/(\d+)C/i);
if (matchesT) t = parseInt(matchesT[1]);
if (matchesP) p = parseInt(matchesP[1]);
if (matchesC) c = parseInt(matchesC[1]);
}
return {
t,
p,
c
};
}
function pagarMantenimientoProyecto(projectId) {
console.log("[PagarManto] Iniciando pago para proyecto ID:", projectId);
if (typeof deptoActual === 'undefined' || !deptoActual) {
alert('Por favor, selecciona primero un departamento haciendo clic en él.');
return;
}
const proyecto = proyectos.find(p => p.id == projectId);
if (!proyecto) {
alert('No se encontró el proyecto.');
console.error("[PagarManto] Proyecto no encontrado con ID:", projectId);
return;
}
console.log("[PagarManto] Proyecto encontrado:", proyecto.nombreProyecto);
// Parse maintenance cost
const cost = parseMantenimiento(proyecto.mantenimiento);
console.log("[PagarManto] Costo parsed:", cost);
// Get department resources
const deptoArr = arreglosDeptos['arr' + deptoActual];
const recT = parseInt(findArrayValue(deptoArr, 'recursoTiempo')) || 0;
const recP = parseInt(findArrayValue(deptoArr, 'recursoPersonal')) || 0;
const recC = parseInt(findArrayValue(deptoArr, 'recursoCapital')) || 0;
console.log("[PagarManto] Recursos depto actual:", {
recT,
recP,
recC
});
// Verificar si LogisticaYSistemas tiene comprada y disponible su habilidad especial
let logisticaHabilidadEspecialDisponible = false;
let elArregloLogistica = null;
const quincena = parseInt($('#etQuincena').val()) || 1;
const trimestreActual = Math.floor((quincena - 1) / 6) + 1;
if (typeof deptoActual !== 'undefined' && deptoActual === 'LogisticaYSistemas') {
elArregloLogistica = arreglosDeptos['arrLogisticaYSistemas'];
if (elArregloLogistica) {
const accionQuincenal = findArrayValue(elArregloLogistica, 'accionEspecialQuincenal');
if (accionQuincenal && accionQuincenal !== '') {
const lastUsed = parseInt(findArrayValue(elArregloLogistica, 'accionEspecialLastUsed')) || 0;
const periodicidad = parseInt(findArrayValue(elArregloLogistica, 'accionEspecialPeriodicidad')) || 2;
if (lastUsed === 0 || quincena >= lastUsed + periodicidad) {
logisticaHabilidadEspecialDisponible = true;
}
}
}
}
const deptoNombre = findArrayValue(deptoArr, 'nombre');
if (logisticaHabilidadEspecialDisponible) {
const dialogMsg = `El departamento de Logística y Sistemas tiene disponible la Habilidad Especial para omitir un mantenimiento sin costo.<br><br>` +
`¿Deseas usar la Habilidad Especial para omitir el mantenimiento del proyecto "${proyecto.nombreProyecto}" <b>sin gastar recursos</b>,<br>` +
`o prefieres realizar un pago normal gastando recursos (${proyecto.mantenimiento})?`;
$('<div></div>')
.html(dialogMsg)
.dialog({
modal: true,
title: 'Pagar Mantenimiento - Logística y Sistemas',
width: 480,
buttons: {
"Usar Habilidad (Sin costo)": function() {
$(this).dialog('close');
// Mark project maintenance as paid in the current trimester
proyecto.mantenimientoPagadoEnTrimestre = trimestreActual;
// Update skill cooldown
setArrayValue(elArregloLogistica, 'accionEspecialLastUsed', quincena);
// Save and update UI
actualizarResumenUI();
actualizarTarjetaDepto('LogisticaYSistemas'); // Refresca la tarjeta en la UI
var audio = new Audio('./Microwave_bell.wav');
audio.play();
alert(`Se ha omitido el mantenimiento del proyecto "${proyecto.nombreProyecto}" sin costo utilizando la Habilidad Especial de Logística y Sistemas.`);
if (typeof guardarDatosEnServidor === 'function') {
guardarDatosEnServidor();
}
},
"Pagar Normal (Con costo)": function() {
$(this).dialog('close');
ejecutarPagoNormal(proyecto, cost, deptoArr, recT, recP, recC, deptoNombre, trimestreActual, quincena);
},
"Cancelar": function() {
$(this).dialog('close');
}
}
});
} else {
ejecutarPagoNormal(proyecto, cost, deptoArr, recT, recP, recC, deptoNombre, trimestreActual, quincena);
}
}
function ejecutarPagoNormal(proyecto, cost, deptoArr, recT, recP, recC, deptoNombre, trimestreActual, quincena) {
if (recT < cost.t || recP < cost.p || recC < cost.c) {
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` +
`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>?`;
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);
// Mark project maintenance as paid in the current trimester
proyecto.mantenimientoPagadoEnTrimestre = trimestreActual;
console.log("[PagarManto] Asignado mantenimientoPagadoEnTrimestre a:", trimestreActual);
// Save and update UI
actualizarTarjetaDepto(deptoActual);
console.log("[PagarManto] Tarjeta departamento actualizada.");
actualizarResumenUI();
console.log("[PagarManto] UI de resumen actualizada.");
var audio = new Audio('./Microwave_bell.wav');
audio.play();
alert(`Mantenimiento pagado con éxito para el proyecto "${proyecto.nombreProyecto}" desde el departamento ${deptoNombre}. El proyecto continuará en proceso en el siguiente trimestre.`);
if (typeof guardarDatosEnServidor === 'function') {
console.log("[PagarManto] Guardando sesión en el servidor...");
guardarDatosEnServidor();
}
}
}, 450);
}
function desecharProyecto(projectId) {
const proyecto = proyectos.find(p => p.id == projectId);
if (!proyecto) {
alert('No se encontró el proyecto.');
return;
}
const penalizacionVal = parseInt(proyecto.penalizacion) || 0;
const penalizacionFormateada = formatCurrency(penalizacionVal);
const quincena = parseInt($('#etQuincena').val()) || 1;
const trimestreActual = Math.floor((quincena - 1) / 6) + 1;
// Verificar si Operaciones tiene comprada y disponible su habilidad especial
let operacionesHabilidadEspecialDisponible = false;
let elArregloOperaciones = null;
if (typeof deptoActual !== 'undefined' && deptoActual === 'Operaciones') {
elArregloOperaciones = arreglosDeptos['arrOperaciones'];
if (elArregloOperaciones) {
const accionQuincenal = findArrayValue(elArregloOperaciones, 'accionEspecialQuincenal');
if (accionQuincenal && accionQuincenal !== '') {
const lastUsed = parseInt(findArrayValue(elArregloOperaciones, 'accionEspecialLastUsed')) || 0;
const periodicidad = parseInt(findArrayValue(elArregloOperaciones, 'accionEspecialPeriodicidad')) || 2;
if (lastUsed === 0 || quincena >= lastUsed + periodicidad) {
operacionesHabilidadEspecialDisponible = true;
}
}
}
}
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>?`;
$('<div></div>')
.html(dialogMsg)
.dialog({
modal: true,
title: 'Desechar Proyecto - Operaciones',
width: 480,
buttons: {
"Usar Habilidad (Sin costo)": function() {
$(this).dialog('close');
// Mark project as discarded
proyecto.estatus = 'descartado';
// Update skill cooldown
setArrayValue(elArregloOperaciones, 'accionEspecialLastUsed', quincena);
// Save and update UI
actualizarResumenUI();
actualizarTarjetaDepto('Operaciones'); // Refresca la tarjeta del depto en la UI
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.`);
if (typeof guardarDatosEnServidor === 'function') {
guardarDatosEnServidor();
}
},
"Desechar Normal (Con costo)": function() {
$(this).dialog('close');
ejecutarDesechoNormal(proyecto, penalizacionVal, penalizacionFormateada, trimestreActual);
},
"Cancelar": function() {
$(this).dialog('close');
}
}
});
} else {
ejecutarDesechoNormal(proyecto, penalizacionVal, penalizacionFormateada, trimestreActual);
}
}
function ejecutarDesechoNormal(proyecto, penalizacionVal, penalizacionFormateada, trimestreActual) {
// Validar que el acumulado general acumulado hasta el trimestre actual sea suficiente
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` +
`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>` +
`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';
// 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}.`);
if (typeof guardarDatosEnServidor === 'function') {
guardarDatosEnServidor();
}
}
}, 450);
}
// Delegación de eventos para los botones de pagar y desechar
$(document).on('click', '.btn-pagar-mantenimiento', function() {
const id = $(this).data('id');
pagarMantenimientoProyecto(id);
});
$(document).on('click', '.btn-desechar-proyecto', function() {
const id = $(this).data('id');
desecharProyecto(id);
});
// El listener para el click en .departamentos ya está en jsCode.js y funcionará ahora
</script>