"use client";
import { useState, useEffect } from "react";
// ==========================================
// 0. BASE DE DATOS SIMULADA (Para que todo funcione antes de Firebase)
// ==========================================
const clientesIniciales = [
{ id: 1, nombre: "Sociedad de Mejoras Públicas", nit: "890.801.123-4", telefono: "3001234567", correo: "contacto@smp.com", direccion: "Cra 23 # 50-20", ltv: 4500000 },
{ id: 2, nombre: "Café El Ruíz", nit: "900.222.333-1", telefono: "3109876543", correo: "gerencia@cafeelruiz.com", direccion: "Centro Histórico", ltv: 1200000 },
{ id: 3, nombre: "Alcaldía de Manizales", nit: "890.801.111-1", telefono: "3112223344", correo: "compras@manizales.gov.co", direccion: "Plaza de Bolívar", ltv: 850000 }
];
const formatoPesos = (valor) => new Intl.NumberFormat('es-CO', { style: 'currency', currency: 'COP', maximumFractionDigits: 0 }).format(valor || 0);
// ==========================================
// 1. DASHBOARD Y MÉTRICAS
// ==========================================
function DashboardMetrics() {
return (
Panel Principal (Resumen)
Ingresos del Mes
{formatoPesos(12500000)}
↑ 15% vs mes anterior
Cuentas por Cobrar (Cartera)
{formatoPesos(2150000)}
Dinero pendiente de cobro
En Producción
5
Trabajos activos en taller
);
}
// ==========================================
// 2. DIRECTORIO DE CLIENTES Y CARTERA
// ==========================================
function ModuloClientes({ clientes }) {
const deudas = [
{ id: 1, cliente: "Sociedad de Mejoras Públicas", proyecto: "Rediseño Web", total: 4500000, abono: 2250000, saldo: 2250000 },
{ id: 2, cliente: "Café El Ruíz", proyecto: "Empaques Litografía", total: 1200000, abono: 600000, saldo: 600000 }
];
return (
Gestión de Clientes y Cartera
Directorio de Clientes
| Cliente / Empresa | NIT / CC | Teléfono | Ventas Totales |
{clientes.map(cli => (
| {cli.nombre} | {cli.nit} | {cli.telefono} | {formatoPesos(cli.ltv)} |
))}
Cuentas por Cobrar
{deudas.map(deuda => (
{deuda.cliente}
{deuda.proyecto}
Valor: {formatoPesos(deuda.total)}Abono: {formatoPesos(deuda.abono)}
Debe: {formatoPesos(deuda.saldo)}
))}
);
}
// ==========================================
// 3. CRM DE COTIZACIONES
// ==========================================
function ModuloCRM() {
const [cotizaciones, setCotizaciones] = useState([
{ id: 1, cliente: "Sociedad de Mejoras Públicas", proyecto: "Rediseño Web", total: 4500000, estado: "produccion" },
{ id: 2, cliente: "Café El Ruíz", proyecto: "Empaques", total: 1200000, estado: "enviada" },
{ id: 3, cliente: "Alcaldía de Manizales", proyecto: "Pendones", total: 850000, estado: "aceptada" },
{ id: 4, cliente: "Constructora XYZ", proyecto: "Vallas", total: 3200000, estado: "rechazada" },
{ id: 5, cliente: "Boutique María", proyecto: "Tarjetas", total: 350000, estado: "finalizada" },
]);
const estados = [
{ id: "enviada", nombre: "Enviadas", color: "bg-blue-100 text-blue-800" },
{ id: "aceptada", nombre: "Aceptadas", color: "bg-yellow-100 text-yellow-800" },
{ id: "produccion", nombre: "En Producción", color: "bg-purple-100 text-purple-800" },
{ id: "finalizada", nombre: "Finalizadas", color: "bg-green-100 text-green-800" },
{ id: "rechazada", nombre: "Rechazadas", color: "bg-red-100 text-red-800" },
];
return (
CRM de Seguimiento
{estados.map(est => (
{est.nombre} ({cotizaciones.filter(c => c.estado === est.id).length})
{cotizaciones.filter(c => c.estado === est.id).map(cot => (
{cot.cliente}
{cot.proyecto}
{formatoPesos(cot.total)}
))}
))}
);
}
// ==========================================
// 4. COTIZADOR, OP Y PROPUESTA COMERCIAL
// ==========================================
function CotizadorMulti({ costoPapelInicial, clientesBD }) {
const [tipoServicio, setTipoServicio] = useState("litografia");
const [clienteIdSeleccionado, setClienteIdSeleccionado] = useState("nuevo");
const [cliente, setCliente] = useState(""); const [nit, setNit] = useState(""); const [telefono, setTelefono] = useState(""); const [direccion, setDireccion] = useState(""); const [correo, setCorreo] = useState(""); const [descripcion, setDescripcion] = useState("");
useEffect(() => {
if (clienteIdSeleccionado !== "nuevo") {
const cli = clientesBD.find(c => c.id === Number(clienteIdSeleccionado));
if (cli) { setCliente(cli.nombre); setNit(cli.nit); setTelefono(cli.telefono); setCorreo(cli.correo); setDireccion(cli.direccion); }
} else { setCliente(""); setNit(""); setTelefono(""); setCorreo(""); setDireccion(""); }
}, [clienteIdSeleccionado, clientesBD]);
const [margenGanancia, setMargenGanancia] = useState(33.3);
const [costoPapel, setCostoPapel] = useState(costoPapelInicial || ""); const [costoPlanchas, setCostoPlanchas] = useState(""); const [costoTiraje, setCostoTiraje] = useState("");
const [ancho, setAncho] = useState(""); const [alto, setAlto] = useState(""); const [costoMetro, setCostoMetro] = useState(""); const [costoEstructura, setCostoEstructura] = useState("");
const [horas, setHoras] = useState(""); const [valorHora, setValorHora] = useState("");
const [costoDiseno, setCostoDiseno] = useState(""); const [costoAcabados, setCostoAcabados] = useState(""); const [costoTroquelado, setCostoTroquelado] = useState(""); const [costoCorte, setCostoCorte] = useState(""); const [costoEnvio, setCostoEnvio] = useState(""); const [costoInstalacion, setCostoInstalacion] = useState("");
const [abono, setAbono] = useState("");
const [costoTotal, setCostoTotal] = useState(0); const [precioVenta, setPrecioVenta] = useState(0);
const [mostrarPropuesta, setMostrarPropuesta] = useState(false); const [mostrarOP, setMostrarOP] = useState(false);
useEffect(() => { if (costoPapelInicial) setCostoPapel(costoPapelInicial); }, [costoPapelInicial]);
useEffect(() => {
let costoServicio = 0;
if (tipoServicio === "litografia") costoServicio = Number(costoPapel) + Number(costoPlanchas) + Number(costoTiraje);
else if (tipoServicio === "gran_formato") costoServicio = (Number(ancho) * Number(alto) * Number(costoMetro)) + Number(costoEstructura);
else if (tipoServicio === "agencia") costoServicio = Number(horas) * Number(valorHora);
const costoBaseTotal = costoServicio + Number(costoAcabados) + Number(costoTroquelado) + Number(costoCorte) + Number(costoDiseno) + Number(costoEnvio) + Number(costoInstalacion);
setCostoTotal(costoBaseTotal);
const margenDecimal = Number(margenGanancia) / 100;
setPrecioVenta(margenDecimal >= 1 ? 0 : costoBaseTotal / (1 - margenDecimal));
}, [tipoServicio, costoPapel, costoPlanchas, costoTiraje, ancho, alto, costoMetro, costoEstructura, horas, valorHora, costoAcabados, costoTroquelado, costoCorte, costoDiseno, costoEnvio, costoInstalacion, margenGanancia]);
const saldoPendiente = precioVenta - (Number(abono) || 0);
const inputStyle = "w-full border border-gray-300 p-2 rounded text-gray-900 bg-white font-medium focus:ring-2 focus:ring-blue-500 outline-none";
if (mostrarOP) {
return (
ORDEN DE PRODUCCIÓN
Uso Interno
OP - {Math.floor(Math.random() * 9000) + 1000}
{new Date().toLocaleDateString('es-CO')}
Datos del Cliente
Cliente: {cliente}
Tel: {telefono}
Tipo de Trabajo
{tipoServicio}
Especificaciones
{descripcion || "Sin descripción."}
);
}
if (mostrarPropuesta) {
return (

e.target.style.display='none'} />
TOPRINTT.
Manizales
{new Date().toLocaleDateString('es-CO')}
{cliente || "Cliente"}
{nit&&
NIT: {nit}
}{telefono&&
Tel: {telefono}
}{correo&&
Correo: {correo}
}
Valor Total:{formatoPesos(precioVenta)}
{Number(abono)>0 && (
Anticipo:- {formatoPesos(abono)}
)}
Saldo Pendiente:{formatoPesos(saldoPendiente)}
);
}
return (
Cotizador Comercial
Costo
{formatoPesos(costoTotal)}
Total Venta
{formatoPesos(precioVenta)}
);
}
// ==========================================
// 5. CALCULADORA DE MONTAJE Y PAPEL
// ==========================================
function CalculadoraCortes({ irACotizador }) {
const [tipoPapel, setTipoPapel] = useState("Propalcote 300g"); const [papelOtro, setPapelOtro] = useState("");
const [cantidadPliegos, setCantidadPliegos] = useState(""); const [precioPliego, setPrecioPliego] = useState("");
const [pliegoAncho, setPliegoAncho] = useState(70); const [pliegoAlto, setPliegoAlto] = useState(100);
const [corteAncho, setCorteAncho] = useState(9); const [corteAlto, setCorteAlto] = useState(5);
const [resultado, setResultado] = useState(null);
const formatoPesos = (valor) => new Intl.NumberFormat('es-CO', { style: 'currency', currency: 'COP', maximumFractionDigits: 0 }).format(valor || 0);
const inputStyle = "w-full border border-gray-300 p-2 rounded text-gray-900 bg-white font-medium focus:ring-2 focus:ring-blue-500 outline-none";
const precioTotalPapel = (Number(cantidadPliegos) || 0) * (Number(precioPliego) || 0);
useEffect(() => {
const pA = parseFloat(pliegoAncho) || 0, pAl = parseFloat(pliegoAlto) || 0, cA = parseFloat(corteAncho) || 0, cAl = parseFloat(corteAlto) || 0;
if (pA <= 0 || pAl <= 0 || cA <= 0 || cAl <= 0) return;
const total1 = Math.floor(pA / cA) * Math.floor(pAl / cAl); const total2 = Math.floor(pA / cAl) * Math.floor(pAl / cA); const areaPliego = pA * pAl;
if (total1 >= total2) setResultado({ piezas: total1, columnas: Math.floor(pA / cA), filas: Math.floor(pAl / cAl), anchoUsado: cA, altoUsado: cAl, porcentaje: (((areaPliego - (total1 * (cA * cAl))) / areaPliego) * 100).toFixed(1) });
else setResultado({ piezas: total2, columnas: Math.floor(pA / cAl), filas: Math.floor(pAl / cA), anchoUsado: cAl, altoUsado: cA, porcentaje: (((areaPliego - (total2 * (cA * cAl))) / areaPliego) * 100).toFixed(1) });
}, [pliegoAncho, pliegoAlto, corteAncho, corteAlto]);
return (
Calculadora de Montajes y Papel
1. Información del Papel
{tipoPapel === "otro" &&
setPapelOtro(e.target.value)} className={inputStyle} />
}
2. Medidas de Corte
{resultado && (
Piezas x Pliego
{resultado.piezas}
Desperdicio
{resultado.porcentaje}%
)}
Vista Previa del Montaje
{resultado && pliegoAncho > 0 && pliegoAlto > 0 ? (
) :
Ingresa medidas
}
);
}
// ==========================================
// 6. MÓDULO DE CAJA (CON HISTORIAL Y FILTROS)
// ==========================================
function ModuloCaja() {
const [baseInicial, setBaseInicial] = useState(100000);
const [movimientos, setMovimientos] = useState([]);
const [tipo, setTipo] = useState("ingreso"); const [descripcion, setDescripcion] = useState(""); const [monto, setMonto] = useState(""); const [metodo, setMetodo] = useState("efectivo");
const [busqueda, setBusqueda] = useState(""); const [tipoFiltro, setTipoFiltro] = useState("todo");
const [fechaExacta, setFechaExacta] = useState(new Date().toISOString().split('T')[0]);
const [mesExacto, setMesExacto] = useState(new Date().toISOString().slice(0, 7));
const formatoPesos = (valor) => new Intl.NumberFormat('es-CO', { style: 'currency', currency: 'COP', maximumFractionDigits: 0 }).format(valor || 0);
const inputStyle = "w-full border border-gray-300 p-2 rounded text-gray-900 bg-white font-medium focus:ring-2 focus:ring-blue-500 outline-none";
const registrarMovimiento = (e) => {
e.preventDefault(); if (!descripcion || !monto) return;
const ahora = new Date();
setMovimientos([{ id: Date.now(), tipo, descripcion, monto: Number(monto), metodo, fechaISO: ahora.toISOString().split('T')[0], hora: ahora.toLocaleTimeString('es-CO', { hour: '2-digit', minute: '2-digit' }) }, ...movimientos]);
setDescripcion(""); setMonto("");
};
const movimientosFiltrados = movimientos.filter(mov => {
const coincideTexto = mov.descripcion.toLowerCase().includes(busqueda.toLowerCase());
let coincideFecha = true;
if (tipoFiltro === "dia") coincideFecha = mov.fechaISO === fechaExacta;
else if (tipoFiltro === "mes") coincideFecha = mov.fechaISO.startsWith(mesExacto);
return coincideTexto && coincideFecha;
});
const saldoActual = Number(baseInicial) + movimientos.filter(m=>m.tipo==="ingreso").reduce((a,c)=>a+c.monto,0) - movimientos.filter(m=>m.tipo==="egreso").reduce((a,c)=>a+c.monto,0);
return (
Flujo de Caja y Reportes
Ingresos (Filtro)
{formatoPesos(movimientosFiltrados.filter(m=>m.tipo==="ingreso").reduce((a,c)=>a+c.monto,0))}
Egresos (Filtro)
{formatoPesos(movimientosFiltrados.filter(m=>m.tipo==="egreso").reduce((a,c)=>a+c.monto,0))}
Saldo Real Global
{formatoPesos(saldoActual)}
);
}
// ==========================================
// 7. NAVEGACIÓN Y "MEMORIA" CENTRAL
// ==========================================
export default function Home() {
const [vistaActiva, setVistaActiva] = useState("cotizador");
const [costoPapelCompartido, setCostoPapelCompartido] = useState("");
const enviarACotizador = (valorTotalCalculado) => {
setCostoPapelCompartido(valorTotalCalculado);
setVistaActiva("cotizador");
};
return (
{vistaActiva === "dashboard" && }
{vistaActiva === "clientes" && }
{vistaActiva === "crm" && }
{vistaActiva === "cotizador" && }
{vistaActiva === "calculadora" && }
{vistaActiva === "caja" && }
);
}