<?php
/**
 * ASISMA CRM - Leads Controller
 */
class LeadsController extends Controller {

    private $leadModel;
    private $empresaModel;
    private $contactoModel;

    public function __construct() {
        $this->leadModel    = new Lead();
        $this->empresaModel = new Empresa();
        $this->contactoModel = new Contacto();
    }

    /**
     * Listar Leads con paginación y filtros
     */
    public function index() {
        $page = (int)$this->input('page', 1);
        if ($page < 1) $page = 1;
        $limit = 20;

        $esAdmin = SessionHelper::isAdmin();

        // Buscador, estado y origen para todos; la segmentación por
        // vertical/marca/producto/franquiciado es solo del admin de la matriz.
        $filters = array(
            'estado'          => $this->input('estado', ''),
            'origen'          => $this->input('origen', ''),
            'q'               => $this->input('q', ''),
            'franquiciado_id' => $esAdmin ? $this->input('franquiciado_id', '') : '',
            'vertical_id'     => $esAdmin ? (int)$this->input('vertical_id', 0) : 0,
            'marca_id'        => $esAdmin ? (int)$this->input('marca_id', 0) : 0,
            'producto_id'     => $esAdmin ? (int)$this->input('producto_id', 0) : 0
        );

        $leads      = $this->leadModel->getLeadsFiltered($page, $limit, $filters);
        $totalLeads = $this->leadModel->getLeadsFilteredCount($filters);
        $totalPages = (int)ceil($totalLeads / $limit);
        // Contadores del embudo (respetan los filtros, menos el de estado)
        $conteoEstados = $this->leadModel->getConteoPorEstado($filters);

        $db = Database::getInstance();
        $franquiciados = array();
        $verticales = array();
        $marcas = array();
        $productos = array();
        if ($esAdmin) {
            $franquiciados = $db->fetchAll(
                "SELECT f.id, ef.nombre_fantasia FROM franquiciados f
                 INNER JOIN empresas_franquiciados ef ON f.empresa_madre_id = ef.id
                 WHERE f.estado = 'activo' ORDER BY ef.nombre_fantasia ASC"
            );
            $verticales = $db->fetchAll("SELECT id, nombre FROM verticales WHERE activa = 1 ORDER BY nombre ASC");
            $marcas     = $db->fetchAll("SELECT id, vertical_id, nombre FROM marcas WHERE activa = 1 ORDER BY nombre ASC");
            $productos  = $db->fetchAll("SELECT id, marca_id, nombre FROM productos WHERE activo = 1 ORDER BY nombre ASC");
        }

        $this->render('leads/index', array(
            'title'       => 'Leads',
            'leads'       => $leads,
            'totalLeads'  => $totalLeads,
            'page'        => $page,
            'totalPages'  => $totalPages,
            'filters'     => $filters,
            'conteoEstados' => $conteoEstados,
            'franquiciados' => $franquiciados,
            'verticales'  => $verticales,
            'marcas'      => $marcas,
            'productos'   => $productos
        ));
    }

    /**
     * Tablero Kanban del embudo (estilo Pipedrive): columnas por estado,
     * arrastrar para cambiar de etapa.
     */
    public function tablero() {
        $db = Database::getInstance();
        $tenantFilter = $this->getTenantFilter('l');
        $esAdmin = SessionHelper::isAdmin();

        // Filtros del tablero: buscador/estado/origen para todos; la
        // segmentación por vertical/marca/producto/franquiciado es solo
        // del admin de la matriz (los franquiciados ya vienen acotados
        // por el tenantFilter).
        $filtros = array(
            'q'               => trim($this->input('q', '')),
            'estado'          => $this->input('estado', ''),
            'origen'          => $this->input('origen', ''),
            'vertical_id'     => $esAdmin ? (int)$this->input('vertical_id', 0) : 0,
            'marca_id'        => $esAdmin ? (int)$this->input('marca_id', 0) : 0,
            'producto_id'     => $esAdmin ? (int)$this->input('producto_id', 0) : 0,
            'franquiciado_id' => $esAdmin ? (int)$this->input('franquiciado_id', 0) : 0
        );

        $condiciones = '';
        $params = array();
        if ($filtros['q'] !== '') {
            // PDO sin emulación: cada placeholder debe ser único
            $condiciones .= " AND (COALESCE(e.razon_social,'') LIKE :f_q1 OR COALESCE(e.rut,'') LIKE :f_q2 OR c.nombre LIKE :f_q3 OR l.telefono_real LIKE :f_q4 OR COALESCE(l.correo_real,'') LIKE :f_q5)";
            $comodin = '%' . $filtros['q'] . '%';
            $params['f_q1'] = $comodin; $params['f_q2'] = $comodin; $params['f_q3'] = $comodin;
            $params['f_q4'] = $comodin; $params['f_q5'] = $comodin;
        }
        if ($filtros['estado'] !== '' && array_key_exists($filtros['estado'], arr_estados_embudo())) {
            $condiciones .= " AND l.estado_embudo = :f_estado";
            $params['f_estado'] = $filtros['estado'];
        }
        if ($filtros['origen'] !== '' && array_key_exists($filtros['origen'], arr_origenes_lead())) {
            $condiciones .= " AND l.origen = :f_origen";
            $params['f_origen'] = $filtros['origen'];
        }
        if ($filtros['vertical_id'])     { $condiciones .= " AND l.vertical_interes = :f_vertical";     $params['f_vertical'] = $filtros['vertical_id']; }
        if ($filtros['marca_id'])        { $condiciones .= " AND l.marca_interes = :f_marca";           $params['f_marca'] = $filtros['marca_id']; }
        if ($filtros['producto_id'])     { $condiciones .= " AND l.producto_interes = :f_producto";     $params['f_producto'] = $filtros['producto_id']; }
        if ($filtros['franquiciado_id']) { $condiciones .= " AND l.franquiciado_id = :f_franquiciado";  $params['f_franquiciado'] = $filtros['franquiciado_id']; }

        $leads = $db->fetchAll(
            "SELECT l.id, l.estado_embudo, l.datos_liberados, l.telefono_real, l.telefono_enmascarado,
                    l.origen, l.updated_at, l.created_at, l.motivo_perdida,
                    c.nombre AS contacto_nombre,
                    e.nombre_fantasia AS empresa_nombre,
                    v.nombre AS vertical_nombre,
                    u.nombre AS vendedor_nombre,
                    ef.nombre_fantasia AS franquiciado_nombre,
                    (SELECT COUNT(*) FROM cotizaciones ct WHERE ct.lead_id = l.id AND ct.activo = 1) AS total_cotizaciones,
                    (SELECT MAX(ct.total) FROM cotizaciones ct WHERE ct.lead_id = l.id AND ct.activo = 1) AS monto_cotizado,
                    DATEDIFF(NOW(), l.updated_at) AS dias_sin_movimiento
             FROM leads l
             INNER JOIN contactos c ON l.contacto_id = c.id
             LEFT JOIN empresas e ON l.empresa_id = e.id
             LEFT JOIN verticales v ON l.vertical_interes = v.id
             LEFT JOIN usuarios u ON l.usuario_asignado_id = u.id
             LEFT JOIN franquiciados f ON l.franquiciado_id = f.id
             LEFT JOIN empresas_franquiciados ef ON f.empresa_madre_id = ef.id
             WHERE l.activo = 1 AND {$tenantFilter}{$condiciones}
             ORDER BY l.updated_at DESC
             LIMIT 500",
            $params
        );

        // Agrupar por estado del embudo
        $columnas = array();
        foreach (array_keys(arr_estados_embudo()) as $estado) {
            $columnas[$estado] = array();
        }
        foreach ($leads as $lead) {
            $estado = isset($columnas[$lead['estado_embudo']]) ? $lead['estado_embudo'] : ESTADO_NUEVO;
            $columnas[$estado][] = $lead;
        }

        // Catálogos para los selects de segmentación (solo admin de la matriz)
        $verticales = array();
        $marcas = array();
        $productos = array();
        $franquiciados = array();
        if ($esAdmin) {
            $verticales = $db->fetchAll("SELECT id, nombre FROM verticales WHERE activa = 1 ORDER BY nombre ASC");
            $marcas     = $db->fetchAll("SELECT id, vertical_id, nombre FROM marcas WHERE activa = 1 ORDER BY nombre ASC");
            $productos  = $db->fetchAll("SELECT id, marca_id, nombre FROM productos WHERE activo = 1 ORDER BY nombre ASC");
            $franquiciados = $db->fetchAll(
                "SELECT f.id, ef.nombre_fantasia FROM franquiciados f
                 INNER JOIN empresas_franquiciados ef ON f.empresa_madre_id = ef.id
                 WHERE f.estado = 'activo' ORDER BY ef.nombre_fantasia ASC"
            );
        }

        $this->render('leads/tablero', array(
            'title'         => 'Tablero de Leads',
            'columnas'      => $columnas,
            'filtros'       => $filtros,
            'verticales'    => $verticales,
            'marcas'        => $marcas,
            'productos'     => $productos,
            'franquiciados' => $franquiciados
        ));
    }

    /**
     * Ver Ficha Completa del Lead
     */
    public function ver() {
        $id = (int)$this->input('id', 0);
        if ($id <= 0) $this->redirect('leads');

        $lead = $this->leadModel->getLeadDetails($id);
        if (!$lead) $this->redirect('leads');

        $db = Database::getInstance();
        $actividades = $db->fetchAll(
            "SELECT a.*, u.nombre as usuario_nombre
             FROM actividades_crm a
             LEFT JOIN usuarios u ON a.usuario_id = u.id
             WHERE a.lead_id = :lead_id
             ORDER BY a.created_at DESC",
            array('lead_id' => $id)
        );

        // Usuarios de la franquicia para reasignación
        $usuariosFranquia = $db->fetchAll(
            "SELECT u.id, u.nombre, u.rol, ef.nombre_fantasia AS empresa_nombre
             FROM usuarios u
             LEFT JOIN empresas_franquiciados ef ON u.empresa_id = ef.id
             WHERE u.franquiciado_id = :fid AND u.activo = 1 AND u.rol = :rol
             ORDER BY u.nombre ASC",
            array('fid' => $lead['franquiciado_id'], 'rol' => ROL_VENDEDOR)
        );

        $db2 = Database::getInstance();
        $verticales = $db2->fetchAll("SELECT id, nombre FROM verticales WHERE activa = 1 ORDER BY nombre ASC");
        $marcas     = $db2->fetchAll("SELECT id, vertical_id, nombre FROM marcas WHERE activa = 1 ORDER BY nombre ASC");
        $productos  = $db2->fetchAll("SELECT id, marca_id, vertical_id, nombre FROM productos WHERE activo = 1 ORDER BY nombre ASC");
        $regiones   = $db2->fetchAll("SELECT id, nombre FROM regiones WHERE activa = 1 ORDER BY nombre ASC");

        // Franquiciados elegibles para DERIVAR el lead: misma región y vertical,
        // con la marca del lead autorizada (regla de alcance). Solo lo ven la
        // matriz y el franquiciado dueño del lead.
        $franquiciadosElegibles = array();
        $puedeDerivar = SessionHelper::isAdmin()
            || (SessionHelper::getFranquiciadoId() && $lead['franquiciado_id'] == SessionHelper::getFranquiciadoId());
        if ($puedeDerivar) {
            $sqlElegibles = "SELECT f.id, ef.nombre_fantasia
                 FROM franquiciados f
                 INNER JOIN empresas_franquiciados ef ON f.empresa_madre_id = ef.id
                 WHERE f.estado = 'activo' AND f.id != :actual";
            $paramsElegibles = array('actual' => (int)$lead['franquiciado_id']);
            if ($lead['region_id'])        { $sqlElegibles .= " AND f.region_id = :region";     $paramsElegibles['region'] = (int)$lead['region_id']; }
            if ($lead['vertical_interes']) { $sqlElegibles .= " AND f.vertical_id = :vertical"; $paramsElegibles['vertical'] = (int)$lead['vertical_interes']; }
            // La restricción de venta manda a nivel de PRODUCTO; sin producto, por marca
            if ($lead['producto_interes']) {
                $sqlElegibles .= " AND " . AlcanceHelper::condicionPuedeVenderProducto('f');
                $paramsElegibles['pid_marca']  = (int)$lead['producto_interes'];
                $paramsElegibles['pid_suelto'] = (int)$lead['producto_interes'];
            } elseif ($lead['marca_interes']) {
                $sqlElegibles .= " AND " . AlcanceHelper::condicionPuedeVenderMarca('f');
                $paramsElegibles['mid_completa'] = (int)$lead['marca_interes'];
            }
            $sqlElegibles .= " ORDER BY ef.nombre_fantasia ASC";
            $franquiciadosElegibles = $db2->fetchAll($sqlElegibles, $paramsElegibles);
        }

        $this->render('leads/ver', array(
            'title'      => 'Detalle: ' . htmlspecialchars($lead['contacto_nombre']),
            'lead'       => $lead,
            'actividades' => $actividades,
            'usuarios'   => $usuariosFranquia,
            'verticales' => $verticales,
            'marcas'     => $marcas,
            'productos'  => $productos,
            'regiones'   => $regiones,
            'franquiciadosElegibles' => $franquiciadosElegibles,
            'puedeDerivar' => $puedeDerivar
        ));
    }

    /**
     * Buscar empresa (y su contacto principal) por RUT — para autocompletar
     * el formulario de nuevo lead. El RUT es la llave de entrada del lead.
     */
    public function buscarPorRut() {
        $rutIngresado = trim($this->input('rut', ''));
        if ($rutIngresado === '') {
            $this->json(array('success' => false, 'message' => 'Debes ingresar un RUT.'));
        }
        if (!validar_rut($rutIngresado)) {
            $this->json(array('success' => true, 'valido' => false, 'existe' => false,
                              'message' => 'El RUT ingresado no es válido (dígito verificador incorrecto).'));
        }
        $rut = normalizar_rut($rutIngresado);
        $empresa = $this->empresaModel->getByRut($rut);
        if (!$empresa) {
            $this->json(array('success' => true, 'valido' => true, 'existe' => false, 'rut' => $rut));
        }

        $db = Database::getInstance();
        $esAdmin = SessionHelper::isAdmin();

        // Si la empresa ya tiene un lead ABIERTO en la misma región y vertical
        // del franquiciado, no se puede ingresar de nuevo (candado anti-duplicado).
        $duplicado = false;
        if (!$esAdmin) {
            $franq = AlcanceHelper::getFranquiciado(SessionHelper::getFranquiciadoId());
            if ($franq) {
                $leadAbierto = $db->fetchOne(
                    "SELECT id FROM leads
                     WHERE empresa_id = :eid AND region_id = :rid AND vertical_interes = :vid
                       AND estado_embudo NOT IN ('ganado', 'perdido', 'invalido') LIMIT 1",
                    array('eid' => $empresa['id'], 'rid' => (int)$franq['region_id'], 'vid' => (int)$franq['vertical_id'])
                );
                $duplicado = $leadAbierto ? true : false;
            }
        }

        // Privacidad entre franquiciados: los datos de contacto de los leads son
        // privados de cada franquicia — solo la matriz recibe el contacto.
        $contacto = null;
        if ($esAdmin) {
            $contacto = $db->fetchOne(
                "SELECT nombre, correo, telefono FROM contactos
                 WHERE empresa_id = :eid ORDER BY es_principal DESC, id ASC LIMIT 1",
                array('eid' => $empresa['id'])
            );
        }

        // Networking: qué franquicias le están vendiendo (leads abiertos) o ya le
        // vendieron (ganados) a esta empresa, con el usuario principal de cada una.
        $vendiendo = array();
        $filasVenta = $db->fetchAll(
            "SELECT f.id AS franquiciado_id, ef.nombre_fantasia AS franquicia, l.estado_embudo,
                    p.nombre AS producto, mk.nombre AS marca, v.nombre AS vertical,
                    c.nombre AS contacto_cliente, u.nombre AS vendedor_asignado
             FROM leads l
             INNER JOIN franquiciados f ON f.id = l.franquiciado_id
             INNER JOIN empresas_franquiciados ef ON ef.id = f.empresa_madre_id
             LEFT JOIN productos p ON p.id = l.producto_interes
             LEFT JOIN marcas mk ON mk.id = l.marca_interes
             LEFT JOIN verticales v ON v.id = l.vertical_interes
             LEFT JOIN contactos c ON c.id = l.contacto_id
             LEFT JOIN usuarios u ON u.id = l.usuario_asignado_id
             WHERE l.empresa_id = :eid
               AND l.estado_embudo NOT IN ('perdido', 'invalido')
             ORDER BY l.created_at DESC",
            array('eid' => $empresa['id'])
        );
        $contactosFranquicia = array();
        foreach ($filasVenta as $fila) {
            $fidVenta = (int)$fila['franquiciado_id'];
            if (!isset($contactosFranquicia[$fidVenta])) {
                $contactosFranquicia[$fidVenta] = $db->fetchOne(
                    "SELECT nombre, email, telefono FROM usuarios
                     WHERE franquiciado_id = :fid AND rol = :rol AND activo = 1
                     ORDER BY id ASC LIMIT 1",
                    array('fid' => $fidVenta, 'rol' => ROL_FRANQUICIADO)
                );
            }
            // Vendedor/contacto de esa franquicia: su usuario principal; si no
            // tiene, el vendedor que lleva el lead (solo el nombre)
            $usuarioPrincipal = $contactosFranquicia[$fidVenta];
            if (!$usuarioPrincipal && !empty($fila['vendedor_asignado'])) {
                $usuarioPrincipal = array('nombre' => $fila['vendedor_asignado'], 'email' => '', 'telefono' => '');
            }
            $queVende = $fila['producto'] ?: ($fila['marca'] ?: $fila['vertical']);
            $vendiendo[] = array(
                'franquicia'       => $fila['franquicia'],
                'producto'         => $queVende,
                'estado'           => $fila['estado_embudo'] === ESTADO_GANADO ? 'Ya es su cliente' : 'En proceso de venta',
                'ganado'           => $fila['estado_embudo'] === ESTADO_GANADO,
                // Privacidad: del cliente solo el NOMBRE de la persona contactada
                // (los telefonos/correos de los leads son privados por franquicia)
                'contacto_cliente' => $fila['contacto_cliente'] ? $fila['contacto_cliente'] : '',
                'contacto'         => $usuarioPrincipal ? array(
                    'nombre'   => $usuarioPrincipal['nombre'],
                    'correo'   => $usuarioPrincipal['email'],
                    'telefono' => $usuarioPrincipal['telefono']
                ) : null
            );
        }

        $this->json(array(
            'success'   => true,
            'valido'    => true,
            'existe'    => true,
            'duplicado' => $duplicado,
            'vendiendo' => $vendiendo,
            'rut'       => $rut,
            'empresa' => array(
                'id'              => (int)$empresa['id'],
                'razon_social'    => $empresa['razon_social'],
                'nombre_fantasia' => $empresa['nombre_fantasia'],
                'region_id'       => $empresa['region_id'] ? (int)$empresa['region_id'] : null
            ),
            'contacto' => $contacto ? array(
                'nombre'   => $contacto['nombre'],
                'telefono' => $contacto['telefono'],
                'correo'   => $contacto['correo']
            ) : null
        ));
    }

    /**
     * Documentación de la API de captación, personalizada por franquiciado:
     * su api_key, sus productos disponibles y ejemplos de body listos para usar.
     */
    public function documentacionApi() {
        if (!SessionHelper::isAdmin() && SessionHelper::getRol() !== ROL_FRANQUICIADO) {
            $this->redirect('leads');
        }
        $db = Database::getInstance();

        $franquiciados = array();
        if (SessionHelper::isAdmin()) {
            $franquiciados = $db->fetchAll(
                "SELECT f.id, ef.nombre_fantasia FROM franquiciados f
                 INNER JOIN empresas_franquiciados ef ON ef.id = f.empresa_madre_id
                 WHERE f.estado = 'activo' ORDER BY ef.nombre_fantasia ASC"
            );
            $franquiciadoId = (int)$this->input('franquiciado_id');
            if (!$franquiciadoId && $franquiciados) $franquiciadoId = (int)$franquiciados[0]['id'];
        } else {
            $franquiciadoId = (int)SessionHelper::getFranquiciadoId();
        }

        $franquicia = $db->fetchOne(
            "SELECT f.id, ef.nombre_fantasia, ef.api_key_leads, f.region_id
             FROM franquiciados f
             INNER JOIN empresas_franquiciados ef ON ef.id = f.empresa_madre_id
             WHERE f.id = :fid",
            array('fid' => $franquiciadoId)
        );

        // Captación POR MARCA (default): solo las marcas COMPLETAS autorizadas.
        // Captación POR PRODUCTO: solo los productos sueltos (franquiciados que
        // venden X productos — a ellos los leads les llegan por producto).
        $marcasCompletas  = array();
        $productosSueltos = array();
        if ($franquicia) {
            $idsMarcas = AlcanceHelper::marcasIds($franquiciadoId);
            if (!empty($idsMarcas)) {
                $marcasCompletas = $db->fetchAll(
                    "SELECT id, nombre FROM marcas WHERE activa = 1
                     AND id IN (" . implode(',', array_map('intval', $idsMarcas)) . ") ORDER BY nombre ASC");
            }
            $idsSueltos = AlcanceHelper::productosSueltosIds($franquiciadoId);
            if (!empty($idsSueltos)) {
                $productosSueltos = $db->fetchAll(
                    "SELECT p.id, p.nombre, p.marca_id, m.nombre as marca_nombre
                     FROM productos p INNER JOIN marcas m ON m.id = p.marca_id
                     WHERE p.activo = 1
                     AND p.id IN (" . implode(',', array_map('intval', $idsSueltos)) . ") ORDER BY p.nombre ASC");
            }
        }
        // Solo la región del franquiciado: sus landings captan leads de su zona
        $regiones = $franquicia
            ? $db->fetchAll("SELECT id, nombre FROM regiones WHERE id = :rid", array('rid' => (int)$franquicia['region_id']))
            : array();

        $this->render('leads/documentacion_api', array(
            'title'            => 'Documentación API de Leads',
            'franquicia'       => $franquicia,
            'franquiciados'    => $franquiciados,
            'franquiciadoId'   => $franquiciadoId,
            'marcasCompletas'  => $marcasCompletas,
            'productosSueltos' => $productosSueltos,
            'regiones'         => $regiones
        ));
    }

    /**
     * Crear Lead - el RUT es el primer dato (valida y enlaza/crea la empresa);
     * nombre y teléfono del contacto son obligatorios
     */
    public function crear() {
        $db = Database::getInstance();

        if ($_SERVER['REQUEST_METHOD'] === 'POST') {
            $nombreContacto = trim($this->input('contacto_nombre', ''));
            $telefono       = normalizar_telefono(trim($this->input('contacto_telefono', '')));
            $correo         = trim($this->input('contacto_email', ''));
            $rut            = trim($this->input('rut', ''));
            $razonSocial    = trim($this->input('razon_social', ''));
            $nombreEmpresa  = trim($this->input('nombre_empresa', ''));
            $regionId       = (int)$this->input('region_id') ?: null;
            $verticalId     = (int)$this->input('vertical_id') ?: null;
            $marcaId        = (int)$this->input('marca_id') ?: null;
            $productoId     = (int)$this->input('producto_id') ?: null;
            $origen         = $this->input('origen', 'manual');
            $notas          = trim($this->input('notas', ''));

            if (empty($nombreContacto) || empty($telefono)) {
                $this->json(array('success' => false, 'message' => 'El nombre y el teléfono/WhatsApp son obligatorios.'));
            }

            if (!empty($correo) && !filter_var($correo, FILTER_VALIDATE_EMAIL)) {
                $this->json(array('success' => false, 'message' => 'El correo ingresado no es válido.'));
            }

            // El RUT es la llave del lead: obligatorio y con dígito verificador válido
            if (empty($rut)) {
                $this->json(array('success' => false, 'message' => 'El RUT del lead es obligatorio.'));
            }
            if (!validar_rut($rut)) {
                $this->json(array('success' => false, 'message' => 'El RUT ingresado no es válido (dígito verificador incorrecto).'));
            }
            $rut = normalizar_rut($rut);
            $empresaExistente = $this->empresaModel->getByRut($rut);
            if (!$empresaExistente && empty($razonSocial)) {
                $this->json(array('success' => false, 'message' => 'La empresa no existe en el CRM: ingresa la razón social para crearla junto al lead.'));
            }

            // Si no es admin, se fuerza el franquiciado Y su alcance:
            // región/vertical propias, y solo marcas/productos autorizados.
            if (!SessionHelper::isAdmin()) {
                $franquiciadoId     = SessionHelper::getFranquiciadoId();
                $usuarioAsignadoId  = SessionHelper::getUsuarioId();

                $franq = AlcanceHelper::getFranquiciado($franquiciadoId);
                if (!$franq) {
                    $this->json(array('success' => false, 'message' => 'Tu cuenta no tiene una franquicia activa asociada.'), 403);
                }
                // Región y vertical no se eligen: son las del franquiciado
                $regionId   = (int)$franq['region_id'];
                $verticalId = (int)$franq['vertical_id'];

                // El producto es la única selección del franquiciado (si tiene uno
                // solo, el formulario lo manda automático); marca y vertical se
                // derivan del producto en el servidor.
                if (!$productoId) {
                    $this->json(array('success' => false, 'message' => 'Debes seleccionar el producto de interés.'));
                }
                $productoFila = $db->fetchOne(
                    "SELECT id, marca_id, vertical_id FROM productos WHERE id = :pid AND activo = 1",
                    array('pid' => $productoId)
                );
                if (!$productoFila) {
                    $this->json(array('success' => false, 'message' => 'El producto seleccionado no existe o está inactivo.'));
                }
                $marcaId = (int)$productoFila['marca_id'];

                $errorAlcance = AlcanceHelper::validarLead($franquiciadoId, $regionId, $verticalId, $marcaId, $productoId);
                if ($errorAlcance) {
                    $this->json(array('success' => false, 'message' => $errorAlcance), 403);
                }
            } else {
                $franquiciadoId    = null;
                $usuarioAsignadoId = null;
            }

            // Candado anti-duplicado: la empresa no puede tener otro lead ABIERTO
            // en la misma región y vertical de interés.
            if ($empresaExistente && $regionId && $verticalId) {
                $leadAbierto = $db->fetchOne(
                    "SELECT id FROM leads
                     WHERE empresa_id = :eid AND region_id = :rid AND vertical_interes = :vid
                       AND estado_embudo NOT IN ('ganado', 'perdido', 'invalido') LIMIT 1",
                    array('eid' => $empresaExistente['id'], 'rid' => $regionId, 'vid' => $verticalId)
                );
                if ($leadAbierto) {
                    $this->json(array('success' => false, 'message' => 'Esta empresa ya tiene un lead activo en esa región y vertical de interés. No se puede ingresar de nuevo.'));
                }
            }

            // Candado DEFINITIVO anti-duplicado (regla 20-07-2026): mismo telefono
            // en la misma region + vertical + producto (o marca si no hay producto)
            // = el lead ya esta siendo atendido. Dentro del mismo franquiciado
            // basta el telefono repetido. El formato del numero no importa.
            $leadAtendido = CandadoLeadsHelper::leadAtendido(
                $regionId, $verticalId, $marcaId, $productoId, $telefono,
                SessionHelper::isAdmin() ? null : $franquiciadoId
            );
            if ($leadAtendido) {
                $this->json(array('success' => false, 'message' => !empty($leadAtendido['mismo_franquiciado'])
                    ? 'Este lead ya está siendo atendido: tu franquicia ya tiene un lead abierto con este número de teléfono.'
                    : 'Este lead ya está siendo atendido: existe un lead abierto con este teléfono en la misma región, vertical y ' . ($productoId ? 'producto' : 'marca') . ' de interés.'));
            }

            // Generar teléfono enmascarado: +569****1234
            $telEnmascarado = enmascarar_telefono($telefono);
            $correoEnmascarado = !empty($correo) ? enmascarar_correo($correo) : null;

            $db->beginTransaction();
            try {
                // 1. Reusar la empresa si el RUT ya existe en el CRM; si no, crearla
                if ($empresaExistente) {
                    $empresaId = $empresaExistente['id'];
                } else {
                    $empresaId = $this->empresaModel->create(array(
                        'rut'           => $rut,
                        'razon_social'  => $razonSocial,
                        'nombre_fantasia'=> $nombreEmpresa !== '' ? $nombreEmpresa : $razonSocial,
                        'region_id'     => $regionId,
                        'email'         => $correo ?: null,
                        'telefono'      => $telefono
                    ));
                }

                // 2. Reusar el contacto de la empresa si coincide el teléfono; si no, crearlo
                $contactoId = null;
                if ($empresaExistente) {
                    $contactoPrevio = $db->fetchOne(
                        "SELECT id FROM contactos WHERE empresa_id = :eid AND telefono = :tel LIMIT 1",
                        array('eid' => $empresaId, 'tel' => $telefono)
                    );
                    if ($contactoPrevio) $contactoId = $contactoPrevio['id'];
                }
                if (!$contactoId) {
                    $contactoId = $this->contactoModel->create(array(
                        'empresa_id'   => $empresaId,
                        'nombre'       => $nombreContacto,
                        'correo'       => $correo ?: null,
                        'telefono'     => $telefono,
                        'es_principal' => $empresaExistente ? 0 : 1
                    ));
                }

                // 3. Auto-asignación por cola rotativa (si hay región y vertical;
                //    el franquiciado debe poder VENDER el producto de interés,
                //    o la marca si el lead no trae producto)
                if (SessionHelper::isAdmin() && $regionId && $verticalId) {
                    $filtroVentaSql = '';
                    $paramsMatch = array('rid' => $regionId, 'vid' => $verticalId);
                    if ($productoId) {
                        $filtroVentaSql = " AND " . AlcanceHelper::condicionPuedeVenderProducto('f');
                        $paramsMatch['pid_marca']  = (int)$productoId;
                        $paramsMatch['pid_suelto'] = (int)$productoId;
                    } elseif ($marcaId) {
                        $filtroVentaSql = " AND " . AlcanceHelper::condicionPuedeVenderMarca('f');
                        $paramsMatch['mid_completa'] = (int)$marcaId;
                    }
                    $matchFranq = $db->fetchOne(
                        "SELECT f.id FROM franquiciados f WHERE f.region_id = :rid AND f.vertical_id = :vid AND f.estado = 'activo'{$filtroVentaSql} ORDER BY f.prioridad DESC, f.id ASC LIMIT 1",
                        $paramsMatch
                    );
                    if ($matchFranq) {
                        $franquiciadoId = $matchFranq['id'];
                        $matchCola = $db->fetchOne(
                            "SELECT c.usuario_id FROM cola_asignacion c INNER JOIN usuarios u ON c.usuario_id = u.id WHERE c.franquiciado_id = :fid AND c.activo = 1 AND u.activo = 1 ORDER BY c.orden ASC LIMIT 1",
                            array('fid' => $franquiciadoId)
                        );
                        if ($matchCola) {
                            $usuarioAsignadoId = $matchCola['usuario_id'];
                            // Rotar la cola
                            $maxOrd = $db->fetchOne("SELECT MAX(orden) as mx FROM cola_asignacion WHERE franquiciado_id = :fid", array('fid' => $franquiciadoId));
                            $db->update('cola_asignacion', array('orden' => (int)$maxOrd['mx'] + 1), "franquiciado_id = :fid AND usuario_id = :uid", array('fid' => $franquiciadoId, 'uid' => $usuarioAsignadoId));
                        }
                    }
                }

                // 4. Crear Lead. Si lo ingresa un franquiciado o vendedor, queda
                // ACEPTADO y tomado por quien lo ingresó al tiro: sin reloj de
                // cascada y con los datos de contacto liberados (los escribió él).
                $autoAceptado  = !SessionHelper::isAdmin();
                $estadoInicial = $usuarioAsignadoId ? ESTADO_ASIGNADO : ESTADO_NUEVO;
                $leadId = $this->leadModel->create(array(
                    'empresa_id'          => $empresaId,
                    'contacto_id'         => $contactoId,
                    'franquiciado_id'     => $franquiciadoId,
                    'usuario_asignado_id' => $usuarioAsignadoId,
                    'origen'              => $origen,
                    'region_id'           => $regionId,
                    'vertical_interes'    => $verticalId,
                    'marca_interes'       => $marcaId,
                    'producto_interes'    => $productoId,
                    'estado_embudo'       => $estadoInicial,
                    'telefono_real'       => $telefono,
                    'telefono_enmascarado'=> $telEnmascarado,
                    'correo_real'         => $correo ?: null,
                    'correo_enmascarado'  => $correoEnmascarado,
                    'datos_liberados'     => $autoAceptado ? 1 : 0,
                    'datos_liberados_at'  => $autoAceptado ? date('Y-m-d H:i:s') : null
                ));

                // 5. Actividad inicial
                $db->insert('actividades_crm', array(
                    'lead_id'    => $leadId,
                    'usuario_id' => SessionHelper::getUsuarioId(),
                    'tipo'       => 'sistema',
                    'direccion'  => 'interna',
                    'asunto'     => 'Lead creado',
                    'detalles'   => 'Lead ingresado al sistema manualmente.'
                ));

                if (!empty($notas)) {
                    $db->insert('actividades_crm', array(
                        'lead_id'    => $leadId,
                        'usuario_id' => SessionHelper::getUsuarioId(),
                        'tipo'       => 'nota',
                        'direccion'  => 'interna',
                        'asunto'     => 'Nota inicial',
                        'detalles'   => $notas
                    ));
                }

                // 6. Registrar asignación formal si se asignó. Ingreso manual de
                // franquiciado/vendedor = aceptada de inmediato (sin timeout).
                if ($usuarioAsignadoId && $franquiciadoId) {
                    $franqData = $db->fetchOne("SELECT tiempo_toma_lead FROM franquiciados WHERE id = :fid", array('fid' => $franquiciadoId));
                    $timeout   = $franqData ? (int)$franqData['tiempo_toma_lead'] : 300;
                    $db->insert('asignaciones_lead', array(
                        'lead_id'          => $leadId,
                        'franquiciado_id'  => $franquiciadoId,
                        'usuario_id'       => $usuarioAsignadoId,
                        'estado'           => $autoAceptado ? 'aceptado' : 'pendiente',
                        'timeout_segundos' => $timeout,
                        'respondido_at'    => $autoAceptado ? date('Y-m-d H:i:s') : null
                    ));
                    $db->insert('actividades_crm', array(
                        'lead_id'    => $leadId,
                        'usuario_id' => null,
                        'tipo'       => 'sistema',
                        'direccion'  => 'interna',
                        'asunto'     => $autoAceptado ? 'Tomado por quien lo ingresó' : 'Asignado automáticamente',
                        'detalles'   => $autoAceptado
                            ? 'Lead ingresado manualmente: quedó aceptado y asignado a quien lo ingresó, con los datos de contacto liberados.'
                            : 'Lead asignado al ejecutivo por cola rotativa.'
                    ));

                    // Auditoria: si la asignacion quedo PENDIENTE, se esta
                    // NOTIFICANDO a ese usuario — queda registrado a quien
                    if (!$autoAceptado) {
                        $franquiciaFila = $db->fetchOne(
                            "SELECT ef.nombre_fantasia FROM empresas_franquiciados ef
                             INNER JOIN franquiciados fr ON fr.empresa_madre_id = ef.id
                             WHERE fr.id = :fid",
                            array('fid' => (int)$franquiciadoId)
                        );
                        $vendedorFila = $db->fetchOne(
                            "SELECT nombre, email FROM usuarios WHERE id = :id",
                            array('id' => (int)$usuarioAsignadoId)
                        );
                        AuditHelper::log('notificacion_lead', 'asignaciones_lead', $leadId, null, array(
                            'lead_id'          => (int)$leadId,
                            'franquiciado_id'  => (int)$franquiciadoId,
                            'franquicia'       => $franquiciaFila ? $franquiciaFila['nombre_fantasia'] : null,
                            'usuario_id'       => (int)$usuarioAsignadoId,
                            'usuario'          => $vendedorFila ? $vendedorFila['nombre'] : null,
                            'usuario_email'    => $vendedorFila ? $vendedorFila['email'] : null,
                            'timeout_segundos' => $timeout
                        ));
                    }
                }

                AuditHelper::log('create_lead', 'leads', $leadId, null, array('contacto' => $nombreContacto, 'telefono' => $telEnmascarado));
                $db->commit();

                $this->json(array('success' => true, 'message' => 'Lead creado exitosamente.', 'redirect' => APP_URL . '/leads/ver?id=' . $leadId));

            } catch (Exception $e) {
                $db->rollBack();
                error_log("Error al crear lead: " . $e->getMessage());
                $this->json(array('success' => false, 'message' => 'Error de base de datos al guardar el lead.'));
            }
        }

        // GET: catálogos según alcance (matriz ve todo; franquiciado solo lo suyo)
        list($regiones, $verticales, $marcas, $productos) = $this->catalogosSegunAlcance($db);

        $this->render('leads/crear', array(
            'title'     => 'Nuevo Lead',
            'regiones'  => $regiones,
            'verticales'=> $verticales,
            'marcas'    => $marcas,
            'productos' => $productos
        ));
    }

    /**
     * Catálogos para formularios de lead: la matriz ve todo; el franquiciado
     * solo su región, su vertical, sus marcas asignadas y los productos de ellas.
     */
    private function catalogosSegunAlcance($db) {
        if (SessionHelper::isAdmin()) {
            return array(
                $db->fetchAll("SELECT id, nombre FROM regiones WHERE activa = 1 ORDER BY nombre ASC"),
                $db->fetchAll("SELECT id, nombre FROM verticales WHERE activa = 1 ORDER BY nombre ASC"),
                $db->fetchAll("SELECT id, vertical_id, nombre FROM marcas WHERE activa = 1 ORDER BY nombre ASC"),
                $db->fetchAll("SELECT id, marca_id, vertical_id, nombre, precio_sugerido FROM productos WHERE activo = 1 ORDER BY nombre ASC")
            );
        }

        $franquiciadoId = SessionHelper::getFranquiciadoId();
        $franq = AlcanceHelper::getFranquiciado($franquiciadoId);
        if (!$franq) {
            return array(array(), array(), array(), array());
        }

        $regiones   = $db->fetchAll("SELECT id, nombre FROM regiones WHERE id = :id", array('id' => $franq['region_id']));
        $verticales = $db->fetchAll("SELECT id, nombre FROM verticales WHERE id = :id", array('id' => $franq['vertical_id']));
        $marcas     = AlcanceHelper::marcas($franquiciadoId);
        $productos  = AlcanceHelper::productos($franquiciadoId);

        return array($regiones, $verticales, $marcas, $productos);
    }

    /**
     * Editar Lead
     */
    public function editar() {
        $id = (int)$this->input('id', 0);
        if ($id <= 0) $this->redirect('leads');

        $lead = $this->leadModel->getLeadDetails($id);
        if (!$lead) $this->redirect('leads');

        $db = Database::getInstance();

        if ($_SERVER['REQUEST_METHOD'] === 'POST') {
            $nombreContacto = trim($this->input('contacto_nombre', ''));
            $correo         = trim($this->input('contacto_email', ''));
            $regionId       = (int)$this->input('region_id') ?: null;
            $verticalId     = (int)$this->input('vertical_id') ?: null;
            $marcaId        = (int)$this->input('marca_id') ?: null;
            $productoId     = (int)$this->input('producto_id') ?: null;
            $origen         = $this->input('origen', 'manual');

            // El TELEFONO y los DATOS DE LA EMPRESA no se modifican desde el
            // lead (regla 20-07): el telefono es la llave del candado, y la
            // empresa se administra en su propia ficha. Se ignora lo que venga.
            $telefono = $lead['telefono_real'];

            // El formulario ya no pide vertical/marca por separado (se eliminó la
            // segmentación): se derivan del producto de interés. Sin producto,
            // se conserva la segmentación que el lead ya tenía.
            if ($productoId) {
                $infoProducto = $db->fetchOne(
                    "SELECT p.marca_id, m.vertical_id FROM productos p JOIN marcas m ON m.id = p.marca_id WHERE p.id = ?",
                    array($productoId)
                );
                if ($infoProducto) {
                    $marcaId    = (int)$infoProducto['marca_id'];
                    $verticalId = (int)$infoProducto['vertical_id'];
                }
            } else {
                if (!$marcaId)    $marcaId    = $lead['marca_interes'] ? (int)$lead['marca_interes'] : null;
                if (!$verticalId) $verticalId = $lead['vertical_interes'] ? (int)$lead['vertical_interes'] : null;
            }

            if (empty($nombreContacto) || empty($telefono)) {
                $this->json(array('success' => false, 'message' => 'El nombre y el teléfono son obligatorios.'));
            }

            if (!empty($correo) && !filter_var($correo, FILTER_VALIDATE_EMAIL)) {
                $this->json(array('success' => false, 'message' => 'El correo ingresado no es válido.'));
            }

            // Alcance del franquiciado: región/vertical fijas y marcas/productos autorizados
            if (!SessionHelper::isAdmin()) {
                $franquiciadoSesion = SessionHelper::getFranquiciadoId();
                $franqAlcance = AlcanceHelper::getFranquiciado($franquiciadoSesion);
                if (!$franqAlcance) {
                    $this->json(array('success' => false, 'message' => 'Tu cuenta no tiene una franquicia activa asociada.'), 403);
                }
                $regionId   = (int)$franqAlcance['region_id'];
                $verticalId = (int)$franqAlcance['vertical_id'];

                $errorAlcance = AlcanceHelper::validarLead($franquiciadoSesion, $regionId, $verticalId, $marcaId, $productoId);
                if ($errorAlcance) {
                    $this->json(array('success' => false, 'message' => $errorAlcance), 403);
                }
            }

            $db->beginTransaction();
            try {
                // La empresa NO se toca desde el lead: se conserva la enlazada
                $empresaId = $lead['empresa_id'] ?: null;

                // Actualizar contacto (solo nombre y correo; el telefono se conserva)
                $this->contactoModel->update($lead['contacto_id'], array(
                    'nombre' => $nombreContacto,
                    'correo' => $correo ?: null
                ));

                $correoEnmascarado = !empty($correo) ? enmascarar_correo($correo) : null;

                $this->leadModel->update($id, array(
                    'origen'               => $origen,
                    'region_id'            => $regionId,
                    'vertical_interes'     => $verticalId,
                    'marca_interes'        => $marcaId,
                    'producto_interes'     => $productoId,
                    'correo_real'          => $correo ?: null,
                    'correo_enmascarado'   => $correoEnmascarado
                ));

                $db->insert('actividades_crm', array(
                    'lead_id'    => $id,
                    'usuario_id' => SessionHelper::getUsuarioId(),
                    'tipo'       => 'sistema',
                    'direccion'  => 'interna',
                    'asunto'     => 'Datos actualizados',
                    'detalles'   => 'Se modificaron los datos del lead.'
                ));

                AuditHelper::log('edit_lead', 'leads', $id, array('contacto' => $lead['contacto_nombre']), array('contacto' => $nombreContacto));
                $db->commit();
                $this->json(array('success' => true, 'message' => 'Lead actualizado.', 'redirect' => APP_URL . '/leads/ver?id=' . $id));

            } catch (Exception $e) {
                $db->rollBack();
                error_log("Error al editar lead: " . $e->getMessage());
                $this->json(array('success' => false, 'message' => 'Error de base de datos al guardar cambios.'));
            }
        }

        list($regiones, $verticales, $marcas, $productos) = $this->catalogosSegunAlcance($db);

        $this->render('leads/editar', array(
            'title'     => 'Editar Lead',
            'lead'      => $lead,
            'regiones'  => $regiones,
            'verticales'=> $verticales,
            'marcas'    => $marcas,
            'productos' => $productos
        ));
    }

    /**
     * JSON: asignaciones PENDIENTES del usuario logueado, para la alerta de
     * NUEVO LEAD en toda la interfaz (banner bajo el logo + modal).
     * Ver el lead NO lo acepta: los datos se liberan solo al tomarlo.
     */
    public function notificacionPendiente() {
        $db = Database::getInstance();
        $pendientes = $db->fetchAll(
            "SELECT al.id AS asignacion_id, l.id AS lead_id, l.origen,
                    e.nombre_fantasia AS empresa, r.nombre AS region,
                    COALESCE(p.nombre, mk.nombre, 'Sin producto') AS interes,
                    al.timeout_segundos,
                    TIMESTAMPDIFF(SECOND, NOW(), DATE_ADD(al.asignado_at, INTERVAL al.timeout_segundos SECOND)) AS segundos_restantes
             FROM asignaciones_lead al
             INNER JOIN leads l ON l.id = al.lead_id
             LEFT JOIN empresas e ON e.id = l.empresa_id
             LEFT JOIN regiones r ON r.id = l.region_id
             LEFT JOIN productos p ON p.id = l.producto_interes
             LEFT JOIN marcas mk ON mk.id = l.marca_interes
             WHERE al.usuario_id = :uid AND al.estado = 'pendiente'
               AND l.activo = 1 AND l.estado_embudo NOT IN ('ganado', 'perdido', 'invalido')
             ORDER BY al.asignado_at ASC",
            array('uid' => (int)SessionHelper::getUsuarioId())
        );
        $this->json(array('success' => true, 'pendientes' => $pendientes));
    }

    /**
     * Liberar datos del lead (teléfono y correo reales) al franquiciado asignado
     */
    public function liberarDatos() {
        if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
            $this->json(array('success' => false, 'message' => 'Método no soportado.'), 405);
        }

        $leadId = (int)$this->input('lead_id');
        if ($leadId <= 0) {
            $this->json(array('success' => false, 'message' => 'ID inválido.'));
        }

        $lead = $this->leadModel->getById($leadId);
        if (!$lead) {
            $this->json(array('success' => false, 'message' => 'Lead no encontrado.'));
        }

        // Solo el franquiciado asignado o admin puede liberar datos
        if (!SessionHelper::isAdmin() && $lead['franquiciado_id'] != SessionHelper::getFranquiciadoId()) {
            $this->json(array('success' => false, 'message' => 'Sin permisos para liberar los datos de este lead.'), 403);
        }

        $db = Database::getInstance();
        $db->update('leads', array(
            'datos_liberados'    => 1,
            'datos_liberados_at' => date('Y-m-d H:i:s')
        ), "id = :id", array('id' => $leadId));

        // Marcar asignación como aceptada
        $db->update('asignaciones_lead', array(
            'estado'        => 'aceptado',
            'respondido_at' => date('Y-m-d H:i:s')
        ), "lead_id = :lid AND estado = 'pendiente'", array('lid' => $leadId));

        $db->insert('actividades_crm', array(
            'lead_id'    => $leadId,
            'usuario_id' => SessionHelper::getUsuarioId(),
            'tipo'       => 'sistema',
            'direccion'  => 'interna',
            'asunto'     => 'Datos liberados',
            'detalles'   => 'El franquiciado aceptó el lead y accedió a los datos de contacto reales.'
        ));

        AuditHelper::log('liberar_datos_lead', 'leads', $leadId, array('datos_liberados' => 0), array('datos_liberados' => 1));

        $this->json(array(
            'success'  => true,
            'message'  => 'Datos liberados. Ya puedes ver el teléfono y correo real del lead.',
            'telefono' => $lead['telefono_real'],
            'correo'   => $lead['correo_real']
        ));
    }

    /**
     * Guardar Actividad / Nota
     */
    public function guardarNota() {
        if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
            $this->json(array('success' => false, 'message' => 'Método no soportado.'), 405);
        }

        $leadId   = (int)$this->input('lead_id');
        $tipo     = $this->input('tipo', 'nota');
        $asunto   = $this->input('asunto', 'Nota manual');
        $detalles = $this->input('detalles', '');

        if ($leadId <= 0 || empty($detalles)) {
            $this->json(array('success' => false, 'message' => 'Los detalles de la actividad son requeridos.'));
        }

        $lead = $this->leadModel->getById($leadId);
        if (!$lead || (!SessionHelper::isAdmin() && $lead['franquiciado_id'] != SessionHelper::getFranquiciadoId())) {
            $this->json(array('success' => false, 'message' => 'Sin permisos para modificar este lead.'));
        }

        $db = Database::getInstance();
        $actId = $db->insert('actividades_crm', array(
            'lead_id'    => $leadId,
            'usuario_id' => SessionHelper::getUsuarioId(),
            'tipo'       => $tipo,
            'direccion'  => 'interna',
            'asunto'     => $asunto,
            'detalles'   => $detalles
        ));

        if ($actId) {
            AuditHelper::log('add_activity', 'actividades_crm', $actId, null, array('lead_id' => $leadId, 'tipo' => $tipo));

            // Estado automático: registrar una interacción real con el cliente
            // (llamada, correo, whatsapp, reunión) avanza el lead a "Contactado"
            if (in_array($tipo, array('llamada', 'email', 'whatsapp', 'reunion'))) {
                EmbudoHelper::avanzarAutomatico($leadId, ESTADO_CONTACTADO, 'se registró una actividad de tipo ' . $tipo . '.');
            }

            $this->json(array(
                'success' => true,
                'message' => 'Actividad guardada correctamente.',
                'data'    => array(
                    'id'            => $actId,
                    'usuario_nombre'=> SessionHelper::getNombre(),
                    'created_at'    => date('d-m-Y H:i')
                )
            ));
        } else {
            $this->json(array('success' => false, 'message' => 'Error al guardar la actividad.'));
        }
    }

    /**
     * Cambiar Estado del Embudo de Ventas (AJAX)
     */
    public function cambiarEstado() {
        if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
            $this->json(array('success' => false, 'message' => 'Método no soportado.'), 405);
        }

        $leadId       = (int)$this->input('lead_id');
        $nuevoEstado  = $this->input('estado');
        $motivoPerdida = $this->input('motivo_perdida', null);
        $notaPerdida   = $this->input('nota_perdida', null);

        if ($leadId <= 0 || empty($nuevoEstado)) {
            $this->json(array('success' => false, 'message' => 'Datos incompletos.'));
        }

        if (!array_key_exists($nuevoEstado, arr_estados_embudo())) {
            $this->json(array('success' => false, 'message' => 'Estado no válido.'));
        }

        $lead = $this->leadModel->getById($leadId);
        if (!$lead || (!SessionHelper::isAdmin() && $lead['franquiciado_id'] != SessionHelper::getFranquiciadoId())) {
            $this->json(array('success' => false, 'message' => 'Sin permisos para modificar este lead.'));
        }

        $db = Database::getInstance();
        $db->beginTransaction();

        try {
            $dataUpdate = array('estado_embudo' => $nuevoEstado);

            if ($nuevoEstado === ESTADO_PERDIDO) {
                if (empty($motivoPerdida)) {
                    $this->json(array('success' => false, 'message' => 'Debe indicar el motivo de la pérdida.'));
                }
                $dataUpdate['motivo_perdida'] = $motivoPerdida;
                $dataUpdate['nota_perdida']   = $notaPerdida;
            }

            $this->leadModel->update($leadId, $dataUpdate);

            $estadosArr  = arr_estados_embudo();
            $estadoLabel = isset($estadosArr[$nuevoEstado]) ? $estadosArr[$nuevoEstado]['label'] : $nuevoEstado;
            $detalle     = "Estado cambiado a: " . $estadoLabel;
            if ($nuevoEstado === ESTADO_PERDIDO) {
                $motivos = arr_motivos_perdida();
                $detalle .= ". Motivo: " . (isset($motivos[$motivoPerdida]) ? $motivos[$motivoPerdida] : $motivoPerdida);
            }

            $db->insert('actividades_crm', array(
                'lead_id'    => $leadId,
                'usuario_id' => SessionHelper::getUsuarioId(),
                'tipo'       => 'sistema',
                'direccion'  => 'interna',
                'asunto'     => 'Cambio de estado',
                'detalles'   => $detalle
            ));

            // Si GANADO → crear operación post-venta
            if ($nuevoEstado === ESTADO_GANADO) {
                $opExiste = $db->fetchOne("SELECT id FROM operaciones_marca WHERE lead_id = :lid", array('lid' => $leadId));
                if (!$opExiste && $lead['marca_interes']) {
                    $db->insert('operaciones_marca', array(
                        'lead_id'                => $leadId,
                        'marca_id'               => $lead['marca_interes'],
                        'franquiciado_id'        => $lead['franquiciado_id'],
                        'estado_implementacion'  => OP_CREAR_CLIENTE,
                        'tipo_venta'             => TIPO_VENTA_UNICA
                    ));
                    $db->insert('actividades_crm', array(
                        'lead_id'    => $leadId,
                        'usuario_id' => null,
                        'tipo'       => 'sistema',
                        'direccion'  => 'interna',
                        'asunto'     => 'Operación post-venta iniciada',
                        'detalles'   => 'Lead ganado. Ficha de operaciones creada.'
                    ));
                }
            }

            AuditHelper::log('change_lead_state', 'leads', $leadId, array('estado' => $lead['estado_embudo']), array('estado' => $nuevoEstado));
            $db->commit();

        } catch (Exception $e) {
            $db->rollBack();
            error_log("Error cambiar estado lead: " . $e->getMessage());
            $this->json(array('success' => false, 'message' => 'Error al actualizar el estado.'));
        }

        // Integraciones externas al GANAR (fuera de la transacción: son llamadas HTTP)
        $avisos = array();
        if ($nuevoEstado === ESTADO_GANADO && $lead['estado_embudo'] !== ESTADO_GANADO) {
            try {
                $resCE = IntegracionesHelper::provisionarClienteControlEmpresas($lead);
                IntegracionesHelper::registrarResultado($leadId, 'Provisión Control Empresas', $resCE);
                if (!$resCE['success']) $avisos[] = $resCE['detalle'];
            } catch (Exception $e) {
                error_log('Error integración Control Empresas: ' . $e->getMessage());
                $avisos[] = 'Error inesperado en la integración con Control Empresas.';
            }

            try {
                $resCliente = IntegracionesHelper::crearClienteEnFranquiciado($lead);
                IntegracionesHelper::registrarResultado($leadId, 'Cliente en cartera del franquiciado (CE)', $resCliente);
                if (!$resCliente['success']) $avisos[] = $resCliente['detalle'];
            } catch (Exception $e) {
                error_log('Error registrando cliente en franquiciado CE: ' . $e->getMessage());
                $avisos[] = 'Error inesperado al registrar el cliente en la cartera del franquiciado.';
            }

            try {
                $resFact = IntegracionesHelper::notificarFacturacion($lead);
                IntegracionesHelper::registrarResultado($leadId, 'Notificación de facturación', $resFact);
                if (!$resFact['success']) $avisos[] = $resFact['detalle'];
            } catch (Exception $e) {
                error_log('Error integración facturación: ' . $e->getMessage());
                $avisos[] = 'Error inesperado al notificar la API de facturación.';
            }
        }

        $mensaje = 'Estado actualizado correctamente.';
        if (!empty($avisos)) {
            $mensaje .= ' Avisos: ' . implode(' | ', $avisos);
        }
        $this->json(array('success' => true, 'message' => $mensaje, 'avisos' => $avisos));
    }

    /**
     * Asignación Manual de Lead a Ejecutivo (AJAX).
     * SOLO el superadmin de la matriz (regla 21-07): franquiciados y
     * vendedores no reasignan; los leads se mueven por la rotación.
     */
    public function asignarManual() {
        if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
            $this->json(array('success' => false, 'message' => 'Método no soportado.'), 405);
        }
        if (!SessionHelper::isAdmin()) {
            $this->json(array('success' => false, 'message' => 'Solo el administrador de la matriz puede reasignar leads.'), 403);
        }

        $leadId            = (int)$this->input('lead_id');
        $usuarioAsignadoId = (int)$this->input('usuario_asignado_id');

        if ($leadId <= 0 || $usuarioAsignadoId <= 0) {
            $this->json(array('success' => false, 'message' => 'Datos incompletos.'));
        }

        $lead = $this->leadModel->getById($leadId);
        if (!$lead || (!SessionHelper::isAdmin() && $lead['franquiciado_id'] != SessionHelper::getFranquiciadoId())) {
            $this->json(array('success' => false, 'message' => 'Sin permisos para modificar este lead.'));
        }

        $db = Database::getInstance();
        $ejecutivo = $db->fetchOne("SELECT id, nombre, franquiciado_id FROM usuarios WHERE id = :id AND activo = 1", array('id' => $usuarioAsignadoId));
        if (!$ejecutivo || (!SessionHelper::isAdmin() && $ejecutivo['franquiciado_id'] != SessionHelper::getFranquiciadoId())) {
            $this->json(array('success' => false, 'message' => 'Ejecutivo no válido para esta franquicia.'));
        }

        $db->beginTransaction();
        try {
            $this->leadModel->update($leadId, array(
                'usuario_asignado_id' => $usuarioAsignadoId,
                'estado_embudo'       => ESTADO_ASIGNADO
            ));

            $db->insert('actividades_crm', array(
                'lead_id'    => $leadId,
                'usuario_id' => SessionHelper::getUsuarioId(),
                'tipo'       => 'sistema',
                'direccion'  => 'interna',
                'asunto'     => 'Reasignación manual',
                'detalles'   => 'Asignado manualmente al ejecutivo: ' . $ejecutivo['nombre']
            ));

            $db->insert('asignaciones_lead', array(
                'lead_id'         => $leadId,
                'franquiciado_id' => $ejecutivo['franquiciado_id'] ?: $lead['franquiciado_id'],
                'usuario_id'      => $usuarioAsignadoId,
                'estado'          => 'aceptado',
                'respondido_at'   => date('Y-m-d H:i:s')
            ));

            AuditHelper::log('assign_lead_manual', 'leads', $leadId, array('usuario' => $lead['usuario_asignado_id']), array('usuario' => $usuarioAsignadoId));
            $db->commit();
            $this->json(array('success' => true, 'message' => 'Vendedor asignado correctamente.'));

        } catch (Exception $e) {
            $db->rollBack();
            error_log("Error al reasignar lead: " . $e->getMessage());
            $this->json(array('success' => false, 'message' => 'Error al registrar reasignación.'));
        }
    }

    /**
     * Derivar el lead a OTRO franquiciado que comparta región, vertical y la
     * marca/producto del lead (regla de alcance). SOLO el superadmin de la
     * matriz (regla 21-07). El lead queda sin vendedor en el destino.
     */
    public function reasignarFranquiciado() {
        if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
            $this->json(array('success' => false, 'message' => 'Método no soportado.'), 405);
        }
        if (!SessionHelper::isAdmin()) {
            $this->json(array('success' => false, 'message' => 'Solo el administrador de la matriz puede derivar leads.'), 403);
        }

        $leadId    = (int)$this->input('lead_id');
        $destinoId = (int)$this->input('franquiciado_id');
        if ($leadId <= 0 || $destinoId <= 0) {
            $this->json(array('success' => false, 'message' => 'Datos incompletos.'));
        }

        $lead = $this->leadModel->getById($leadId);
        if (!$lead) {
            $this->json(array('success' => false, 'message' => 'Lead no encontrado.'));
        }
        if (!SessionHelper::isAdmin() && $lead['franquiciado_id'] != SessionHelper::getFranquiciadoId()) {
            $this->json(array('success' => false, 'message' => 'Sin permisos para derivar este lead.'), 403);
        }
        if ((int)$lead['franquiciado_id'] === $destinoId) {
            $this->json(array('success' => false, 'message' => 'El lead ya pertenece a ese franquiciado.'));
        }

        $db = Database::getInstance();
        $destino = AlcanceHelper::getFranquiciado($destinoId);
        if (!$destino || $destino['estado'] !== 'activo') {
            $this->json(array('success' => false, 'message' => 'El franquiciado de destino no existe o está inactivo.'));
        }

        // Regla de la reunión: solo entre franquiciados con la MISMA región,
        // vertical y que comercialicen la marca/producto del lead.
        $errorAlcance = AlcanceHelper::validarLead(
            $destinoId,
            (int)$lead['region_id'],
            (int)$lead['vertical_interes'],
            (int)$lead['marca_interes'],
            (int)$lead['producto_interes']
        );
        if ($errorAlcance) {
            $this->json(array('success' => false, 'message' => 'El destino no puede atender este lead: ' . $errorAlcance), 422);
        }

        $nombreDestino = $db->fetchOne(
            "SELECT ef.nombre_fantasia FROM franquiciados f
             INNER JOIN empresas_franquiciados ef ON f.empresa_madre_id = ef.id
             WHERE f.id = :id",
            array('id' => $destinoId)
        );
        $nombreDestino = $nombreDestino ? $nombreDestino['nombre_fantasia'] : ('#' . $destinoId);

        $db->beginTransaction();
        try {
            // El lead cambia de franquicia y queda SIN vendedor en el destino
            $this->leadModel->update($leadId, array(
                'franquiciado_id'     => $destinoId,
                'usuario_asignado_id' => null,
                'estado_embudo'       => ESTADO_ASIGNADO
            ));

            $db->insert('actividades_crm', array(
                'lead_id'    => $leadId,
                'usuario_id' => SessionHelper::getUsuarioId(),
                'tipo'       => 'sistema',
                'direccion'  => 'interna',
                'asunto'     => 'Derivado a otro franquiciado',
                'detalles'   => 'Lead derivado al franquiciado ' . $nombreDestino . ' por ' . SessionHelper::getNombre() . '. Queda sin vendedor asignado en el destino.'
            ));

            AuditHelper::log('reasignar_franquiciado', 'leads', $leadId,
                array('franquiciado' => $lead['franquiciado_id']),
                array('franquiciado' => $destinoId));
            $db->commit();
            $this->json(array('success' => true, 'message' => 'Lead derivado a ' . $nombreDestino . '. Quedó sin vendedor asignado en esa franquicia.'));

        } catch (Exception $e) {
            $db->rollBack();
            error_log('Error al derivar lead a otro franquiciado: ' . $e->getMessage());
            $this->json(array('success' => false, 'message' => 'Error al derivar el lead.'));
        }
    }

}
