diff --git a/frontend/src/pages/Plantillas.jsx b/frontend/src/pages/Plantillas.jsx
new file mode 100644
index 0000000..d28a326
--- /dev/null
+++ b/frontend/src/pages/Plantillas.jsx
@@ -0,0 +1,633 @@
+import { useState, useEffect, useMemo, useRef } from "react";
+import { api } from "../lib/api";
+
+// Lista desplegable con casillas para marcar varios. Cierra al hacer click fuera.
+function MultiSelect({ label, icon, items, selected, onToggle, color = "#2563eb", bgOn = "#eff6ff", width = 240 }) {
+ const [abierto, setAbierto] = useState(false);
+ const ref = useRef(null);
+ useEffect(() => {
+ const fn = (e) => { if (ref.current && !ref.current.contains(e.target)) setAbierto(false); };
+ document.addEventListener("mousedown", fn);
+ return () => document.removeEventListener("mousedown", fn);
+ }, []);
+ const n = selected.length;
+ const resumen = n === 0 ? label : n === 1
+ ? (items.find((it) => it.v === selected[0])?.txt || `1 ${label}`)
+ : `${n} seleccionados`;
+ return (
+
+ {!ocultarTitulo &&
📄 Plantillas
}
+
+ {/* PASO 1 — Elegir plantilla */}
+
+
+ 1
+ Elige la plantilla
+
+ {filtradas.length} disponibles
+
+
+
La plantilla que se enviará a los teléfonos.
+
+
setBusca(e.target.value)}
+ placeholder="🔍 Buscar por nombre o categoría..."
+ style={{ padding: "9px 14px", border: "1px solid #cbd5e1", borderRadius: 10,
+ fontSize: 13, width: 280 }} />
+
+ {cargando ? (
+
Cargando plantillas...
+ ) : error ? (
+
{error}
+ ) : (
+
{ setSel(p); setResultado(null); }} colorCat={colorCat} />
+ )}
+
+
+ {sel && (
+
+ ✓ Plantilla:
+ {sel.nombre}
+
+ )}
+
+
+ {/* PASO 2 — Armar base y enviar plantilla */}
+
+
+ 2
+ Armar base y enviar plantilla
+
+
+
Marca campañas, aplica filtros y exporta o envía a esa base.
+
+
+
+ {/* Fila 1: campaña + asesor */}
+
+
+
🏷️ Campaña(s)
+
({ v: c.codigo, txt: c.codigo, title: (c.frases || []).join(" | ") }))}
+ selected={codsSel} onToggle={toggleCod}
+ color="#2563eb" bgOn="#eff6ff" width="100%" />
+
+
+
👤 Asesor
+
({ v: a, txt: a }))]}
+ selected={asesSel} onToggle={toggleAsesor}
+ color="#10b981" bgOn="#ecfdf5" width="100%" />
+
+
+
+ {/* Fila 2: estado + orden */}
+
+
+
🏷️ Estado / Objeción
+
({ v: e, txt: e }))}
+ selected={estSel} onToggle={toggleEstado}
+ color="#7c3aed" bgOn="#f5f3ff" width="100%" />
+
+
+
📅 Ordenar por fecha
+
+
+
+
+ {/* Fila 3: cantidad a enviar/exportar */}
+
+
+ 🔢 Cantidad a enviar / exportar {conteo !== null && (1–{conteo})}
+
+
+ setLimite(e.target.value)}
+ placeholder={conteo !== null ? `1 – ${conteo}` : "cantidad"}
+ style={{ padding: "9px 14px", borderRadius: 10, fontSize: 13, fontWeight: 600, flex: 1, minWidth: 0,
+ background: "#fff", color: "#0f172a",
+ border: `1px solid ${limiteError ? "#ef4444" : "#cbd5e1"}` }} />
+
+
+ {limiteError && (
+
⚠️ {limiteError}
+ )}
+
+
+ {/* Barra de resumen + acciones */}
+
+
+ {codsSel.length === 0 ? "📱 marca una campaña"
+ : contando ? "📱 contando…"
+ : conteo === null ? "📱 —"
+ : `📱 ${conteo} teléfono(s)`}
+
+
+
+
+
+
+ Se enviará:
+ {sel ? {sel.nombre}
+ : elige la plantilla en el paso 1 ↑}
+
+ {baseMsg && (
+
{baseMsg}
+ )}
+ {resEnvioBase && (
+
+ {resEnvioBase.error ? resEnvioBase.error : (
+ <>✅ Enviados: {resEnvioBase.enviados} ·
+ ❌ Fallidos: {(resEnvioBase.fallidos || []).length} ·
+ ⚠️ Sin código 51: {(resEnvioBase.invalidos || []).length}>
+ )}
+
+ )}
+
+
+ {/* PASO 3 — Importar base y enviar plantilla */}
+
+
+ 3
+ Importar base y enviar plantilla
+ opcional
+
+
Si ya tienes una lista de teléfonos en Excel, impórtala y envía la plantilla.
+
+
+
+ {archivo && (
+
+ {archivo} · {telefonos.length} número(s)
+
+ )}
+
+
+
+ {resultado && (
+
+ {resultado.error ? resultado.error : (
+ <>
+ ✅ Enviados:
{resultado.enviados} ·
+ ❌ Fallidos:
{(resultado.fallidos || []).length} ·
+ ⚠️ Sin código 51 (no enviados):
{(resultado.invalidos || []).length}
+ {(resultado.invalidos || []).length > 0 && (
+
+ Rechazados por no tener código 51: {resultado.invalidos.join(", ")}
+
+ )}
+ >
+ )}
+
+ )}
+
+ El Excel debe tener una columna TELEFONO. Solo se envía a números peruanos con código 51 (ej. 51966319975).
+
+
+
+ {/* Pop-up de confirmación de envío directo a la base */}
+ {confirmEnvio && (
+
setConfirmEnvio(null)}
+ style={{ position: "fixed", inset: 0, background: "rgba(15,23,42,.35)", zIndex: 100,
+ display: "flex", alignItems: "center", justifyContent: "center" }}>
+
e.stopPropagation()}
+ style={{ background: "#fff", borderRadius: 14, padding: "24px 28px", maxWidth: 380,
+ boxShadow: "0 20px 50px rgba(15,23,42,.25)", textAlign: "center" }}>
+
🚀
+
+ Enviar la plantilla {sel && sel.nombre} a
{confirmEnvio.n} teléfono(s)?
+
+
Esta acción no se puede deshacer.
+
+
+
+
+
+
+ )}
+
+ {/* Mini pop-up de advertencia */}
+ {aviso && (
+
setAviso("")}
+ style={{ position: "fixed", inset: 0, background: "rgba(15,23,42,.35)", zIndex: 100,
+ display: "flex", alignItems: "center", justifyContent: "center" }}>
+
e.stopPropagation()}
+ style={{ background: "#fff", borderRadius: 14, padding: "22px 26px", maxWidth: 340,
+ boxShadow: "0 20px 50px rgba(15,23,42,.25)", textAlign: "center" }}>
+
⚠️
+
{aviso}
+
+
+
+ )}
+
+ );
+}