Seleccionar página

MARKETING DIGITAL CON RESULTADOS

¿Necesitas más clientes potenciales?

thumbnail

Diseñamos estrategias digitales 360° que transforman tu inversión publicitaria en clientes prospecto calificados y ventas reales para tu empresa

Si te identificas con alguna de estas situaciones

¿Quieres pautar en Redes sociales o Google y no sabes cómo hacerlo?

¿Tus campañas digitales no generan los resultados que esperas?

¿Los leads que captas son de baja calidad?

¿No sabes como crear avisos que den buenos resultados?

¿Tu página landing no esta generando los resultados que esperas?

¿Los costos de tus campañas son demasiado altos?

ENTONCES NUESTRO SERVICIO 360° ES PARA TI

1 - PLANEAMOS TU ESTRATEGIA

  • Proyectamos tus metas con base en tus recursos.
  • Diseñamos el camino que te llevará a lograr tus resultados.

2 - CREAMOS AVISOS, LANDING Y VSL

Diseñamos los avisos, página landing y hasta el VSL que te llevará a lograr tus resultados.

3 - CREAMOS Y ADMINISTRAMOS TUS CAMPAÑAS

Diseñamos, creamos, administramos y optimizamos tus campañas en META (Facebook e Instagram) y Google 

4 - OPTIMIZAMOS TU EMBUDO CONTINUAMENTE

Continuamente optimizamos todo el embudo de ventas para disminuir costos y mejorar los resultados.

No somos una agencia, somos consultores especializados: Diseñamos, desarrollamos y controlamos todo el ciclo de venta digital optimizándolo continuamente para maximizar tus resultados.

15 AÑOS AYUDANDO A MÁS DE 100 EMPRESAS Y EMPRENDEDORES

He trabajado durante mas de 8 años con SIM DIGITAL, para el soporte y asesoramiento en diversos temas de mercadeo recibiendo de su parte un trabajo de alta calidad representado en oportunidad, efectividad e innovación. Quizás una característica que debo destacar es que hace de los objetivos de MI  compañía, SUS objetivos, lo que evidencia el compromiso de su trabajo.

Gloria Chahin

Gerente General, Chekar.co

Somos aliados comerciales de SIM DIGITAL desde hace más de 4 años, para destacar su servicio al cliente y ejecución ágil de actividades solicitadas. Adicionalmente cuentan con un gran conocimiento sobre las tendencias digitales del mercado, siendo muy valiosa su asesoría en la implementación de creación de estrategias que aporten valor para el posicionamiento y ventas de nuestra compañía.

Nadia Soledad

Gerente de Mercadeo, Jorge Cortes y Cia

Si tienes un presupuesto mínimo de USD 400 para tu estrategia digital mensual

DILIGENCIA EL FORMULARIO PARA RECIBIR ASESORÍA

Para conocer tus expectativas, determinar lo que necesitas para lograr tus objetivos y saber cómo te podemos ayudar … EN ESTA ASESORÍA NO TE VAMOS A VENDER NADAsolo queremos saber si te podemos ayudar.

Presupuesto de inversión

import React, { useMemo, useState, useEffect } from "react"; import * as XLSX from "xlsx"; import { BarChart, Bar, LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip as RTooltip, ResponsiveContainer, Legend, } from "recharts"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { X, UploadCloud, Filter, Trash2, ChevronDown, Beaker } from "lucide-react"; // ===================================== // Helpers // ===================================== function toNumber(val: any): number { if (val === undefined || val === null || val === "") return 0; if (typeof val === "number") return val; const cleaned = String(val).replace(/[^0-9,.-]/g, "").replace(/,/g, "."); const n = parseFloat(cleaned); return isNaN(n) ? 0 : n; } function average(arr: number[]): number { if (!arr.length) return 0; return arr.reduce((a, b) => a + b, 0) / arr.length; } function formatNumber(n: number): string { return n.toLocaleString("es-CO", { maximumFractionDigits: 0 }); } function formatPercent(n: number, maxDigits = 2): string { return `${n.toLocaleString("es-CO", { maximumFractionDigits: maxDigits })}%`; } function formatCurrency(n: number): string { return n.toLocaleString("es-CO", { style: "currency", currency: "COP", maximumFractionDigits: 0, }); } // Map row by column letters (A,B,C,G,J,K,L,N) function mapRowByColumns(rowObjOrArray: any) { if (Array.isArray(rowObjOrArray)) { return { A: rowObjOrArray[0], B: rowObjOrArray[1], C: rowObjOrArray[2], G: toNumber(rowObjOrArray[6]), J: toNumber(rowObjOrArray[9]), K: toNumber(rowObjOrArray[10]), L: toNumber(rowObjOrArray[11]), N: toNumber(rowObjOrArray[13]), }; } const keys = Object.keys(rowObjOrArray); const byName = (nameIncludes: string) => { const key = keys.find((k) => k.toLowerCase().includes(nameIncludes)); return key ? rowObjOrArray[key] : undefined; }; return { A: rowObjOrArray["A"] ?? byName("campaña") ?? byName("campaign") ?? byName("nombre"), B: rowObjOrArray["B"] ?? byName("edad") ?? byName("age"), C: rowObjOrArray["C"] ?? byName("sexo") ?? byName("gender"), G: toNumber(rowObjOrArray["G"] ?? byName("impres") ?? byName("impre")), J: toNumber(rowObjOrArray["J"] ?? byName("resultado") ?? byName("results")), K: toNumber(rowObjOrArray["K"] ?? byName("invers") ?? byName("spend") ?? byName("costo")), L: toNumber(rowObjOrArray["L"] ?? byName("cpm")), N: toNumber(rowObjOrArray["N"] ?? byName("clics") ?? byName("enlace") ?? byName("link")), }; } // ===================================== // UI Subcomponents // ===================================== function Metric({ label, value, hint, tooltipTitle, }: { label: string; value: React.ReactNode; hint?: string; tooltipTitle?: string; }) { return ( {label}
{value}
{hint ?
{hint}
: null}
); } function Chip({ children, onRemove }: { children: React.ReactNode; onRemove?: () => void }) { return ( {children} {onRemove && ( )} ); } function FilterDropdown({ label, options, selected, onToggle, }: { label: string; options: string[]; selected: string[]; onToggle: (v: string) => void; }) { return ( {options.length ? ( options.map((val) => ( onToggle(val)} className="focus:bg-neutral-800 focus:text-white" > {val} )) ) : (
Carga un archivo para ver opciones…
)}
); } // ===================================== // Main Component // ===================================== export default function DashboardExcelCampaigns() { const [rows, setRows] = useState([]); const [filters, setFilters] = useState<{ A: string[]; B: string[]; C: string[] }>({ A: [], B: [], C: [] }); const [fileName, setFileName] = useState(""); // Unique values for dropdowns const uniqueValues = useMemo(() => { const setA = new Set(); const setB = new Set(); const setC = new Set(); rows.forEach((r) => { if (r.A !== undefined && r.A !== null && r.A !== "") setA.add(String(r.A)); if (r.B !== undefined && r.B !== null && r.B !== "") setB.add(String(r.B)); if (r.C !== undefined && r.C !== null && r.C !== "") setC.add(String(r.C)); }); return { A: Array.from(setA).sort((a, b) => a.localeCompare(b)), B: Array.from(setB).sort((a, b) => a.localeCompare(b)), C: Array.from(setC).sort((a, b) => a.localeCompare(b)), }; }, [rows]); // Filtered data const filtered = useMemo(() => { return rows.filter((r) => { const okA = filters.A.length ? filters.A.includes(String(r.A)) : true; const okB = filters.B.length ? filters.B.includes(String(r.B)) : true; const okC = filters.C.length ? filters.C.includes(String(r.C)) : true; return okA && okB && okC; }); }, [rows, filters]); // Metrics const metrics = useMemo(() => { const sumG = filtered.reduce((acc, r) => acc + toNumber(r.G), 0); const sumK = filtered.reduce((acc, r) => acc + toNumber(r.K), 0); const sumJ = filtered.reduce((acc, r) => acc + toNumber(r.J), 0); const sumN = filtered.reduce((acc, r) => acc + toNumber(r.N), 0); const avgL = average(filtered.map((r) => toNumber(r.L)).filter((v) => v > 0)); const ctr = sumG > 0 ? (sumN / sumG) * 100 : 0; const cpr = sumJ > 0 ? sumK / sumJ : 0; const efectPct = sumN > 0 ? (sumJ / sumN) * 100 : 0; return { impresiones: sumG, inversion: sumK, resultados: sumJ, ctr, cpr, cpm: avgL, efectividad: efectPct, }; }, [filtered]); // Charts data (impresiones y resultados agregados por B y C) const chartByAge = useMemo(() => { const map = new Map(); filtered.forEach((r) => { const key = String(r.B ?? ""); const g = map.get(key) || { impresiones: 0, resultados: 0 }; g.impresiones += toNumber(r.G); g.resultados += toNumber(r.J); map.set(key, g); }); return Array.from(map.entries()).map(([name, vals]) => ({ name, impresiones: vals.impresiones, resultados: vals.resultados, })); }, [filtered]); const chartBySex = useMemo(() => { const map = new Map(); filtered.forEach((r) => { const key = String(r.C ?? ""); const g = map.get(key) || { impresiones: 0, resultados: 0 }; g.impresiones += toNumber(r.G); g.resultados += toNumber(r.J); map.set(key, g); }); return Array.from(map.entries()).map(([name, vals]) => ({ name, impresiones: vals.impresiones, resultados: vals.resultados, })); }, [filtered]); // Table rows (AGREGADAS POR CAMPAÑA) const tableData = useMemo(() => { const groups = new Map< string, { impresiones: number; inversion: number; resultados: number; clicks: number; cpmVals: number[] } >(); filtered.forEach((r) => { const camp = String(r.A ?? ""); const g = groups.get(camp) || { impresiones: 0, inversion: 0, resultados: 0, clicks: 0, cpmVals: [] }; g.impresiones += toNumber(r.G); g.inversion += toNumber(r.K); g.resultados += toNumber(r.J); g.clicks += toNumber(r.N); const l = toNumber(r.L); if (l > 0) g.cpmVals.push(l); groups.set(camp, g); }); const rowsAgg = Array.from(groups.entries()).map(([campaña, g]) => { const ctr = g.impresiones > 0 ? (g.clicks / g.impresiones) * 100 : 0; const cpr = g.resultados > 0 ? g.inversion / g.resultados : 0; const cpm = g.cpmVals.length ? average(g.cpmVals) : 0; return { campaña, impresiones: g.impresiones, inversion: g.inversion, resultados: g.resultados, ctr, cpm, cpr }; }); return rowsAgg.sort((a, b) => a.campaña.localeCompare(b.campaña)); }, [filtered]); // ===================== // File handling & actions // ===================== function handleFile(e: React.ChangeEvent) { const file = e.target.files?.[0]; if (!file) return; setFileName(file.name); const reader = new FileReader(); reader.onload = (evt) => { const data = new Uint8Array((evt.target as any).result); const wb = XLSX.read(data, { type: "array" }); const ws = wb.Sheets[wb.SheetNames[0]]; let parsed: any[] = []; try { const aoa = XLSX.utils.sheet_to_json(ws, { header: 1, raw: false }) as any[]; const body = aoa.filter( (row: any) => Array.isArray(row) && row.some((cell: any) => cell !== undefined && cell !== null && String(cell).trim() !== "") ); let startIdx = 0; if (body.length) { const likelyHeader = body[0].every((c: any) => typeof c === "string"); if (likelyHeader) startIdx = 1; } parsed = body.slice(startIdx).map((r: any) => mapRowByColumns(r)); } catch (err) { const objRows = XLSX.utils.sheet_to_json(ws, { defval: "" }); parsed = (objRows as any[]).map((r) => mapRowByColumns(r)); } setRows(parsed); setFilters({ A: [], B: [], C: [] }); }; reader.readAsArrayBuffer(file); } function toggleFilter(key: "A" | "B" | "C", value: string) { setFilters((prev) => { const list = new Set(prev[key]); if (list.has(value)) list.delete(value); else list.add(value); return { ...prev, [key]: Array.from(list) }; }); } function clearFilters() { setFilters({ A: [], B: [], C: [] }); } function clearData() { setRows([]); setFilters({ A: [], B: [], C: [] }); setFileName(""); } // ===================== // Demo data & Tests // ===================== const demoRows = [ { A: "Campaña Alfa", B: "18-24", C: "Femenino", G: 10000, J: 200, K: 500000, L: 50000, N: 800 }, { A: "Campaña Beta", B: "25-34", C: "Masculino", G: 8000, J: 120, K: 300000, L: 45000, N: 560 }, { A: "Campaña Beta", B: "25-34", C: "Femenino", G: 7000, J: 90, K: 270000, L: 42000, N: 420 }, ]; function loadDemo() { setRows(demoRows); setFileName("demo.xlsx (simulado)"); setFilters({ A: [], B: [], C: [] }); } function runDevTests() { // Smoke tests sobre demoRows const sumG = demoRows.reduce((s, r) => s + r.G, 0); // 25000 const sumN = demoRows.reduce((s, r) => s + r.N, 0); // 1780 const sumK = demoRows.reduce((s, r) => s + r.K, 0); // 1,070,000 const sumJ = demoRows.reduce((s, r) => s + r.J, 0); // 410 console.assert(sumG === 25000, `Impresiones esperadas 25000, obtuvo ${sumG}`); console.assert(sumJ === 410, `Resultados esperados 410, obtuvo ${sumJ}`); console.assert(sumK === 1070000, `Inversión esperada 1070000, obtuvo ${sumK}`); const ctr = sumG > 0 ? (sumN / sumG) * 100 : 0; console.assert(Math.round(ctr) === Math.round((1780 / 25000) * 100), `CTR no coincide`); const cpr = sumJ > 0 ? sumK / sumJ : 0; console.assert(Math.round(cpr) === Math.round(1070000 / 410), `CPR no coincide`); const efect = sumN > 0 ? (sumJ / sumN) * 100 : 0; console.assert(Math.round(efect) === Math.round((410 / 1780) * 100), `Efectividad no coincide`); // Test extra: agregación por campaña (no desagregar por edad/sexo) const agg: Record = {}; for (const r of demoRows) { const key = r.A; agg[key] = agg[key] || { G: 0, K: 0, J: 0, N: 0, L: [] }; agg[key].G += r.G; agg[key].K += r.K; agg[key].J += r.J; agg[key].N += r.N; if (r.L > 0) agg[key].L.push(r.L); } const beta = agg["Campaña Beta"]; console.assert(beta.G === 15000 && beta.J === 210, "Agregación por campaña (Beta) incorrecta"); } useEffect(() => { runDevTests(); }, []); // ===================================== // Render // ===================================== return (
{/* Header */}

DASHBOARD DE CAMPAÑAS META - GARANTY SEGUROS.

Carga un Excel y filtra por Nombre de campaña (A), Grupo de edad (B) y Sexo (C). Todo el dashboard se actualiza en tiempo real.

{/* Filters (Dropdowns) */}
toggleFilter("A", v)} /> toggleFilter("B", v)} /> toggleFilter("C", v)} />
{/* Selected chips */}
{filters.A.map((v) => ( toggleFilter("A", v)}> A: {v} ))} {filters.B.map((v) => ( toggleFilter("B", v)}> B: {v} ))} {filters.C.map((v) => ( toggleFilter("C", v)}> C: {v} ))} {!filters.A.length && !filters.B.length && !filters.C.length && ( Sin filtros activos )}
{/* Metrics */}
{/* Charts (4): Impresiones y Resultados por edad/sexo */}
{/* Impresiones por grupo de edad */} Impresiones por grupo de edad (B) formatCurrency(Number(v))} /> {/* Impresiones por sexo */} Impresiones por sexo (C) formatCurrency(Number(v))} /> {/* Resultados por grupo de edad */} Resultados por grupo de edad (B) formatNumber(Number(v))} /> {/* Resultados por sexo */} Resultados por sexo (C) formatNumber(Number(v))} />
{/* Table (agregada por campaña) */} Campañas (orden alfabético por nombre) {tableData.length ? ( tableData.map((r, idx) => ( )) ) : ( )}
Campaña (A) Impresiones (G) Inversión (K) Resultados (J) CTR % CPM Costo/Resultado
{r.campaña} {formatNumber(r.impresiones)} {formatCurrency(r.inversion)} {formatNumber(r.resultados)} {formatPercent(r.ctr)} {formatCurrency(r.cpm)} {formatCurrency(r.cpr)}
Carga un Excel para visualizar la tabla o usa Cargar demo.
{/* Footer note */}

Notas: Este dashboard calcula todas las métricas usando únicamente las filas filtradas. Columnas usadas: A (Campaña), B (Grupo de edad), C (Sexo), G (Impresiones), J (Resultados), K (Inversión), L (CPM), N (Clics en enlace). Se incluyen protecciones contra división por cero.

); }