organizar en carpetas

This commit is contained in:
2026-09-03 16:29:29 -05:00
parent bb24734046
commit 07890d7308
12 changed files with 41 additions and 14 deletions
+195
View File
@@ -0,0 +1,195 @@
// ============ ACCIONES: MENÚ ⋮ Y MODALES (compartido) ============
// Requiere que la página tenga:
// - variable global NOMBRE_HOJA
// - función global cargarMovimientos()
// - elemento #opcionesTipo con <option>s para el select de tipo
let registroEditando = null;
let registroBorrando = null;
// ============ MENÚ ⋮ ============
function crearMenuOpciones(mov) {
const contenedor = document.createElement("div");
contenedor.className = "menu-opciones";
contenedor.innerHTML = `
<button class="btn-opciones" title="Opciones">⋮</button>
<div class="dropdown-opciones">
<button class="opcion-editar">✏️ Editar</button>
<button class="opcion-borrar">🗑️ Borrar</button>
</div>
`;
const btnOpciones = contenedor.querySelector(".btn-opciones");
const dropdown = contenedor.querySelector(".dropdown-opciones");
btnOpciones.addEventListener("click", (e) => {
e.stopPropagation();
// Cerrar otros dropdowns abiertos
document.querySelectorAll(".dropdown-opciones.abierto").forEach(d => {
if (d !== dropdown) d.classList.remove("abierto");
});
dropdown.classList.toggle("abierto");
});
contenedor.querySelector(".opcion-editar").addEventListener("click", () => {
dropdown.classList.remove("abierto");
abrirModalEditar(mov);
});
contenedor.querySelector(".opcion-borrar").addEventListener("click", () => {
dropdown.classList.remove("abierto");
abrirModalBorrar(mov);
});
return contenedor;
}
// Cerrar dropdowns al hacer clic fuera
document.addEventListener("click", () => {
document.querySelectorAll(".dropdown-opciones.abierto").forEach(d => d.classList.remove("abierto"));
});
// ============ MODAL EDITAR ============
function abrirModalEditar(mov) {
registroEditando = mov;
document.getElementById("editFecha").value = formatearFechaInput(mov.Fecha);
// Seleccionar la opción correcta en el select
const selectTipo = document.getElementById("editTipo");
selectTipo.value = mov.Tipo;
document.getElementById("editDescripcion").value = mov["Descripción"];
document.getElementById("editMonto").value = parseInt(mov.Monto) || 0;
document.getElementById("modalEditarFondo").classList.add("abierto");
}
function cerrarModalEditar() {
registroEditando = null;
document.getElementById("modalEditarFondo").classList.remove("abierto");
}
async function guardarEdicion() {
if (!registroEditando) return;
const datos = {
hoja: NOMBRE_HOJA,
id: registroEditando.id,
datos: {
"Fecha": document.getElementById("editFecha").value,
"Tipo": document.getElementById("editTipo").value,
"Descripción": document.getElementById("editDescripcion").value,
"Monto": document.getElementById("editMonto").value
}
};
try {
const respuesta = await fetch("/actualizar", {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(datos)
});
const resultado = await respuesta.json();
if (resultado.error) {
alert("Error: " + resultado.error);
return;
}
cerrarModalEditar();
cargarMovimientos();
} catch (error) {
console.error("Error al actualizar:", error);
alert("Error al actualizar el registro");
}
}
// ============ MODAL BORRAR ============
function abrirModalBorrar(mov) {
registroBorrando = mov;
document.getElementById("borrarDescripcion").textContent =
`"${mov["Descripción"]}" — $${formatearMonto(mov.Monto)}`;
document.getElementById("modalBorrarFondo").classList.add("abierto");
}
function cerrarModalBorrar() {
registroBorrando = null;
document.getElementById("modalBorrarFondo").classList.remove("abierto");
}
async function confirmarBorrar() {
if (!registroBorrando) return;
try {
const respuesta = await fetch("/borrar", {
method: "DELETE",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ hoja: NOMBRE_HOJA, id: registroBorrando.id })
});
const resultado = await respuesta.json();
if (resultado.error) {
alert("Error: " + resultado.error);
return;
}
cerrarModalBorrar();
cargarMovimientos();
} catch (error) {
console.error("Error al borrar:", error);
alert("Error al borrar el registro");
}
}
// ============ AUXILIAR ============
// Convierte fecha al formato YYYY-MM-DD que acepta <input type="date">
function formatearFechaInput(fecha) {
if (!fecha) return "";
const f = new Date(fecha);
const yyyy = f.getFullYear();
const mm = String(f.getMonth() + 1).padStart(2, "0");
const dd = String(f.getDate()).padStart(2, "0");
return `${yyyy}-${mm}-${dd}`;
}
// Cerrar modales al hacer clic en el fondo oscuro
document.addEventListener("DOMContentLoaded", () => {
document.getElementById("modalEditarFondo").addEventListener("click", (e) => {
if (e.target.id === "modalEditarFondo") cerrarModalEditar();
});
document.getElementById("modalBorrarFondo").addEventListener("click", (e) => {
if (e.target.id === "modalBorrarFondo") cerrarModalBorrar();
});
document.getElementById("btnGuardarEdicion").addEventListener("click", guardarEdicion);
document.getElementById("btnCancelarEdicion").addEventListener("click", cerrarModalEditar);
document.getElementById("btnConfirmarBorrar").addEventListener("click", confirmarBorrar);
document.getElementById("btnCancelarBorrar").addEventListener("click", cerrarModalBorrar);
// Botón de sincronización manual en el nav
const btnSync = document.getElementById("btnSync");
if (btnSync) btnSync.addEventListener("click", forzarSync);
});
// ============ SINCRONIZACIÓN MANUAL ============
async function forzarSync() {
const btnSync = document.getElementById("btnSync");
btnSync.disabled = true;
btnSync.textContent = "⏳";
try {
const respuesta = await fetch("/sincronizar", { method: "POST" });
const resultado = await respuesta.json();
if (resultado.pendientes === 0) {
btnSync.textContent = "✅";
} else {
btnSync.textContent = `⚠️ ${resultado.pendientes}`;
}
// Recargar la tabla por si hubo cambios de estado
cargarMovimientos();
} catch (error) {
console.error("Error al sincronizar:", error);
btnSync.textContent = "❌";
}
// Volver al estado normal después de 2 segundos
setTimeout(() => {
btnSync.disabled = false;
btnSync.textContent = "🔄 Sync";
}, 2000);
}
+136
View File
@@ -0,0 +1,136 @@
// ============ CONFIGURACIÓN ============
// Mismo servidor: rutas relativas
const API_URL = "";
const NOMBRE_HOJA = "cajaMenor";
// ============ CARGAR DATOS AL ABRIR LA PÁGINA ============
document.addEventListener("DOMContentLoaded", () => {
cargarMovimientos();
});
// ============ LEER DATOS ============
async function cargarMovimientos() {
const tbody = document.getElementById("tablaMovimientos");
try {
const respuesta = await fetch(`/leer?hoja=${NOMBRE_HOJA}`);
const data = await respuesta.json();
if (data.error) {
tbody.innerHTML = `<tr><td colspan="4" class="sin-datos">Error: ${data.error}</td></tr>`;
return;
}
mostrarMovimientos(data.datos);
calcularTotal(data.datos);
} catch (error) {
tbody.innerHTML = `<tr><td colspan="4" class="sin-datos">Error al cargar datos</td></tr>`;
console.error("Error:", error);
}
}
// ============ MOSTRAR EN LA TABLA ============
function mostrarMovimientos(movimientos) {
const tbody = document.getElementById("tablaMovimientos");
if (movimientos.length === 0) {
tbody.innerHTML = `<tr><td colspan="5" class="sin-datos">No hay movimientos registrados</td></tr>`;
return;
}
tbody.innerHTML = "";
// Mostrar del más reciente al más antiguo (ya viene ordenado del backend)
movimientos.forEach(mov => {
const fila = document.createElement("tr");
const esIngreso = mov.Tipo === "Ingreso";
fila.innerHTML = `
<td>${formatearFecha(mov.Fecha)}</td>
<td><span class="${esIngreso ? 'badge-ingreso' : 'badge-salida'}">${esIngreso ? '💵' : '💸'} ${mov.Tipo}</span></td>
<td>${mov["Descripción"]}</td>
<td class="${esIngreso ? 'monto-ingreso' : 'monto-salida'}">${esIngreso ? '+' : '-'}$${formatearMonto(mov.Monto)}</td>
`;
// Menú de opciones (⋮) con modales
const celdaOpciones = document.createElement("td");
celdaOpciones.appendChild(crearMenuOpciones(mov));
fila.appendChild(celdaOpciones);
tbody.appendChild(fila);
});
}
// ============ CALCULAR TOTAL ============
function calcularTotal(movimientos) {
let total = 0;
movimientos.forEach(mov => {
const monto = parseFloat(mov.Monto) || 0;
if (mov.Tipo === "Ingreso") {
total += monto;
} else {
total -= monto;
}
});
const totalElement = document.getElementById("totalCaja");
totalElement.textContent = `$${formatearMonto(total)}`;
totalElement.className = "total-monto " + (total >= 0 ? "positivo" : "negativo");
}
// ============ GUARDAR NUEVO MOVIMIENTO ============
document.getElementById("formMovimiento").addEventListener("submit", async (e) => {
e.preventDefault();
const btnGuardar = e.target.querySelector("button");
btnGuardar.disabled = true;
btnGuardar.textContent = "⏳ Guardando...";
const datos = {
hoja: NOMBRE_HOJA,
datos: {
"Fecha": document.getElementById("fecha").value,
"Tipo": document.getElementById("tipo").value,
"Descripción": document.getElementById("descripcion").value,
"Monto": document.getElementById("monto").value
}
};
try {
const respuesta = await fetch("/guardar", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(datos)
});
const resultado = await respuesta.json();
if (resultado.error) {
alert("Error: " + resultado.error);
}
// Limpiar formulario y recargar tabla
document.getElementById("formMovimiento").reset();
cargarMovimientos();
} catch (error) {
console.error("Error al guardar:", error);
alert("Error al guardar el registro");
}
btnGuardar.disabled = false;
btnGuardar.textContent = "💾 Guardar";
});
// ============ FUNCIONES AUXILIARES ============
function formatearFecha(fecha) {
if (!fecha) return "";
const f = new Date(fecha);
return f.toLocaleDateString("es-CO", { day: "2-digit", month: "2-digit", year: "numeric" });
}
function formatearMonto(monto) {
const num = parseFloat(monto) || 0;
return num.toLocaleString("es-CO", { minimumFractionDigits: 0, maximumFractionDigits: 0 });
}
+114
View File
@@ -0,0 +1,114 @@
// ============ CONFIGURACIÓN ============
const NOMBRE_HOJA = "libroDiario";
// ============ CARGAR DATOS AL ABRIR LA PÁGINA ============
document.addEventListener("DOMContentLoaded", () => {
cargarMovimientos();
});
// ============ LEER DATOS ============
async function cargarMovimientos() {
const tbody = document.getElementById("tablaMovimientos");
try {
const respuesta = await fetch(`/leer?hoja=${NOMBRE_HOJA}`);
const data = await respuesta.json();
if (data.error) {
tbody.innerHTML = `<tr><td colspan="4" class="sin-datos">Error: ${data.error}</td></tr>`;
return;
}
mostrarMovimientos(data.datos);
} catch (error) {
tbody.innerHTML = `<tr><td colspan="4" class="sin-datos">Error al cargar datos</td></tr>`;
console.error("Error:", error);
}
}
// ============ MOSTRAR EN LA TABLA ============
function mostrarMovimientos(movimientos) {
const tbody = document.getElementById("tablaMovimientos");
if (movimientos.length === 0) {
tbody.innerHTML = `<tr><td colspan="5" class="sin-datos">No hay registros</td></tr>`;
return;
}
tbody.innerHTML = "";
movimientos.forEach(mov => {
const fila = document.createElement("tr");
const esFlor = mov.Tipo === "Personal Flor";
fila.innerHTML = `
<td>${formatearFecha(mov.Fecha)}</td>
<td><span class="${esFlor ? 'badge-ingreso' : 'badge-salida'}">${esFlor ? '🌸' : '📦'} ${mov.Tipo}</span></td>
<td>${mov["Descripción"]}</td>
<td class="monto-salida">$${formatearMonto(mov.Monto)}</td>
`;
// Menú de opciones (⋮) con modales
const celdaOpciones = document.createElement("td");
celdaOpciones.appendChild(crearMenuOpciones(mov));
fila.appendChild(celdaOpciones);
tbody.appendChild(fila);
});
}
// ============ GUARDAR NUEVO MOVIMIENTO ============
document.getElementById("formMovimiento").addEventListener("submit", async (e) => {
e.preventDefault();
const btnGuardar = e.target.querySelector("button");
btnGuardar.disabled = true;
btnGuardar.textContent = "⏳ Guardando...";
const datos = {
hoja: NOMBRE_HOJA,
datos: {
"Fecha": document.getElementById("fecha").value,
"Tipo": document.getElementById("tipo").value,
"Descripción": document.getElementById("descripcion").value,
"Monto": document.getElementById("monto").value
}
};
try {
const respuesta = await fetch("/guardar", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(datos)
});
const resultado = await respuesta.json();
if (resultado.error) {
alert("Error: " + resultado.error);
}
// Limpiar formulario y recargar tabla
document.getElementById("formMovimiento").reset();
cargarMovimientos();
} catch (error) {
console.error("Error al guardar:", error);
alert("Error al guardar el registro");
}
btnGuardar.disabled = false;
btnGuardar.textContent = "💾 Guardar";
});
// ============ FUNCIONES AUXILIARES ============
function formatearFecha(fecha) {
if (!fecha) return "";
const f = new Date(fecha);
return f.toLocaleDateString("es-CO", { day: "2-digit", month: "2-digit", year: "numeric" });
}
function formatearMonto(monto) {
const num = parseFloat(monto) || 0;
return num.toLocaleString("es-CO", { minimumFractionDigits: 0, maximumFractionDigits: 0 });
}