<?php
/**
 * ASISMA CRM - Catalogo Controller (Verticales, Marcas, Productos)
 */
class CatalogoController extends Controller {

    // El catalogo se administra 100% en el CRM (decision de alcance 15-07-2026:
    // se desconecto la sincronizacion con Control Empresas).

    // =====================================================================
    // VERTICALES
    // =====================================================================

    public function verticalesIndex() {
        $db = Database::getInstance();
        // Por defecto solo se listan las ACTIVAS; ?inactivos=1 muestra todo
        $mostrarInactivos = isset($_GET['inactivos']) && $_GET['inactivos'] == '1';
        if (SessionHelper::isAdmin()) {
            $verticales = $db->fetchAll(
                "SELECT * FROM verticales" . ($mostrarInactivos ? "" : " WHERE activa = 1") . " ORDER BY nombre ASC");
        } else {
            // Franquiciado: solo su vertical asignada
            $franq = AlcanceHelper::getFranquiciado(SessionHelper::getFranquiciadoId());
            $verticales = $franq
                ? $db->fetchAll("SELECT * FROM verticales WHERE id = :id" . ($mostrarInactivos ? "" : " AND activa = 1"), array('id' => $franq['vertical_id']))
                : array();
        }
        $this->render('catalogo/verticales', array(
            'title'            => 'Catalogo de Verticales',
            'verticales'       => $verticales,
            'mostrarInactivos' => $mostrarInactivos
        ));
    }

    public function verticalesCrear() {
        if (!$this->isAdmin()) {
            $this->json(array('success' => false, 'message' => 'Sin permisos.'), 403);
        }
        $nombre      = trim(isset($_POST['nombre']) ? $_POST['nombre'] : '');
        $descripcion = trim(isset($_POST['descripcion']) ? $_POST['descripcion'] : '');

        if ($nombre === '') {
            $this->json(array('success' => false, 'message' => 'El nombre es requerido.'));
        }

        $db = Database::getInstance();
        $id = $db->insert('verticales', array(
            'nombre'       => $nombre,
            'descripcion'  => $descripcion,
            'activa'       => 1
        ));
        $this->json(array('success' => true, 'message' => 'Vertical creada correctamente.', 'id' => $id));
    }

    public function verticalesEditar() {
        if (!$this->isAdmin()) {
            $this->json(array('success' => false, 'message' => 'Sin permisos.'), 403);
        }
        $id          = intval(isset($_POST['id']) ? $_POST['id'] : 0);
        $nombre      = trim(isset($_POST['nombre']) ? $_POST['nombre'] : '');
        $descripcion = trim(isset($_POST['descripcion']) ? $_POST['descripcion'] : '');

        if (!$id || $nombre === '') {
            $this->json(array('success' => false, 'message' => 'Datos invalidos.'));
        }

        $db = Database::getInstance();
        $db->update('verticales', array(
            'nombre'       => $nombre,
            'descripcion'  => $descripcion
        ), 'id = :id', array(':id' => $id));
        $this->json(array('success' => true, 'message' => 'Vertical actualizada.'));
    }

    public function verticalesToggle() {
        if (!$this->isAdmin()) {
            $this->json(array('success' => false, 'message' => 'Sin permisos.'), 403);
        }
        $id = intval(isset($_POST['id']) ? $_POST['id'] : 0);
        if (!$id) {
            $this->json(array('success' => false, 'message' => 'ID invalido.'));
        }
        $db   = Database::getInstance();
        $vert = $db->fetchOne("SELECT activa FROM verticales WHERE id = :id", array(':id' => $id));
        if (!$vert) {
            $this->json(array('success' => false, 'message' => 'No encontrada.'));
        }
        $nuevo = $vert['activa'] ? 0 : 1;
        $db->update('verticales', array('activa' => $nuevo), 'id = :id', array(':id' => $id));
        $this->json(array('success' => true, 'message' => $nuevo ? 'Vertical activada.' : 'Vertical desactivada.', 'nuevo_estado' => $nuevo));
    }

    // =====================================================================
    // MARCAS
    // =====================================================================

    public function marcasIndex() {
        $db = Database::getInstance();
        $verticalId = SessionHelper::getVerticalId();
        // Por defecto solo se listan las ACTIVAS; ?inactivos=1 muestra todo
        $mostrarInactivos = isset($_GET['inactivos']) && $_GET['inactivos'] == '1';
        $filtroActivas = $mostrarInactivos ? "" : " AND m.activa = 1";

        if (!SessionHelper::isAdmin() && $verticalId) {
            // Franquiciado: SOLO las marcas donde puede vender (completas o
            // con productos sueltos autorizados)
            $fid = (int)SessionHelper::getFranquiciadoId();
            $marcas = $db->fetchAll(
                "SELECT DISTINCT m.*, v.nombre as vertical_nombre
                 FROM marcas m
                 INNER JOIN verticales v ON m.vertical_id = v.id
                 WHERE (EXISTS (SELECT 1 FROM franquiciado_marcas fm WHERE fm.franquiciado_id = :fid AND fm.marca_id = m.id)
                     OR EXISTS (SELECT 1 FROM franquiciado_productos fp
                                INNER JOIN productos p ON p.id = fp.producto_id
                                WHERE fp.franquiciado_id = :fid2 AND p.marca_id = m.id)){$filtroActivas}
                 ORDER BY m.nombre ASC",
                array(':fid' => $fid, ':fid2' => $fid)
            );
            $verticales = $db->fetchAll(
                "SELECT id, nombre FROM verticales WHERE id = :vid AND activa = 1",
                array(':vid' => $verticalId)
            );
        } else {
            $marcas = $db->fetchAll(
                "SELECT m.*, v.nombre as vertical_nombre
                 FROM marcas m
                 INNER JOIN verticales v ON m.vertical_id = v.id
                 WHERE 1=1{$filtroActivas}
                 ORDER BY m.nombre ASC"
            );
            $verticales = $db->fetchAll("SELECT id, nombre FROM verticales WHERE activa = 1 ORDER BY nombre ASC");
        }

        $this->render('catalogo/marcas', array(
            'title'            => 'Catalogo de Marcas',
            'marcas'           => $marcas,
            'verticales'       => $verticales,
            'mostrarInactivos' => $mostrarInactivos
        ));
    }

    public function marcasCrear() {
        if (!$this->isAdmin()) {
            $this->json(array('success' => false, 'message' => 'Sin permisos.'), 403);
        }
        $verticalId       = intval(isset($_POST['vertical_id']) ? $_POST['vertical_id'] : 0);
        $nombre           = trim(isset($_POST['nombre']) ? $_POST['nombre'] : '');
        $contactoNombre   = trim(isset($_POST['contacto_nombre']) ? $_POST['contacto_nombre'] : '');
        $contactoEmail    = trim(isset($_POST['contacto_email']) ? $_POST['contacto_email'] : '');
        $contactoTelefono = trim(isset($_POST['contacto_telefono']) ? $_POST['contacto_telefono'] : '');

        if (!$verticalId || $nombre === '') {
            $this->json(array('success' => false, 'message' => 'Vertical y nombre son requeridos.'));
        }
        $this->validarContactoMarca($contactoEmail, $contactoTelefono);

        $db = Database::getInstance();
        $id = $db->insert('marcas', array_merge(array(
            'vertical_id'       => $verticalId,
            'nombre'            => $nombre,
            'contacto_nombre'   => $contactoNombre,
            'contacto_email'    => $contactoEmail,
            'contacto_telefono' => $contactoTelefono,
            'activa'            => 1
        ), $this->leerBrandingMarca()));
        $this->json(array('success' => true, 'message' => 'Marca creada correctamente.', 'id' => $id));
    }

    /**
     * Branding de la MARCA desde el POST del modal: identidad corporativa
     * (branding_json), diseño del documento y condiciones comerciales.
     * Aplica a TODO lo que la marca vende (productos y servicios).
     */
    private function leerBrandingMarca() {
        $diseno = isset($_POST['diseno_documento']) ? trim($_POST['diseno_documento']) : 'ejecutivo';
        if (!array_key_exists($diseno, IdentidadHelper::disenos())) $diseno = 'ejecutivo';

        // Condiciones comerciales: "Titulo | texto", una por línea
        $condiciones = array();
        $condTexto = trim(isset($_POST['condiciones']) ? $_POST['condiciones'] : '');
        if ($condTexto !== '') {
            foreach (preg_split('/\r?\n/', $condTexto) as $linea) {
                $linea = trim($linea);
                if ($linea === '') continue;
                $partes = explode('|', $linea, 2);
                $condiciones[] = array(
                    'titulo' => trim($partes[0]),
                    'texto'  => isset($partes[1]) ? trim($partes[1]) : '',
                );
            }
        }

        return array(
            'branding_json'    => IdentidadHelper::codificarBranding(IdentidadHelper::construirDesdePost($_POST)),
            'diseno_documento' => $diseno,
            'condiciones_json' => $condiciones ? json_encode($condiciones, JSON_UNESCAPED_UNICODE) : null,
        );
    }

    public function marcasEditar() {
        if (!$this->isAdmin()) {
            $this->json(array('success' => false, 'message' => 'Sin permisos.'), 403);
        }
        $id               = intval(isset($_POST['id']) ? $_POST['id'] : 0);
        $verticalId       = intval(isset($_POST['vertical_id']) ? $_POST['vertical_id'] : 0);
        $nombre           = trim(isset($_POST['nombre']) ? $_POST['nombre'] : '');
        $contactoNombre   = trim(isset($_POST['contacto_nombre']) ? $_POST['contacto_nombre'] : '');
        $contactoEmail    = trim(isset($_POST['contacto_email']) ? $_POST['contacto_email'] : '');
        $contactoTelefono = trim(isset($_POST['contacto_telefono']) ? $_POST['contacto_telefono'] : '');
        if (!$id || !$verticalId || $nombre === '') {
            $this->json(array('success' => false, 'message' => 'Datos invalidos.'));
        }
        $this->validarContactoMarca($contactoEmail, $contactoTelefono);

        $db = Database::getInstance();
        $db->update('marcas', array_merge(array(
            'vertical_id'       => $verticalId,
            'nombre'            => $nombre,
            'contacto_nombre'   => $contactoNombre,
            'contacto_email'    => $contactoEmail,
            'contacto_telefono' => $contactoTelefono
        ), $this->leerBrandingMarca()), 'id = :id', array(':id' => $id));
        $this->json(array('success' => true, 'message' => 'Marca actualizada.'));
    }

    /**
     * El telefono y el correo del contacto de la marca son OBLIGATORIOS:
     * a ese correo se le notifica cada venta de sus productos.
     */
    private function validarContactoMarca($contactoEmail, $contactoTelefono) {
        if ($contactoTelefono === '' || $contactoEmail === '') {
            $this->json(array('success' => false, 'message' => 'El teléfono y el correo del contacto de la marca son obligatorios: a ese correo se le notifica cada venta de sus productos.'));
        }
        if (!filter_var($contactoEmail, FILTER_VALIDATE_EMAIL)) {
            $this->json(array('success' => false, 'message' => 'El correo del contacto de la marca no es válido.'));
        }
    }

    public function marcasToggle() {
        if (!$this->isAdmin()) {
            $this->json(array('success' => false, 'message' => 'Sin permisos.'), 403);
        }
        $id = intval(isset($_POST['id']) ? $_POST['id'] : 0);
        if (!$id) {
            $this->json(array('success' => false, 'message' => 'ID invalido.'));
        }
        $db    = Database::getInstance();
        $marca = $db->fetchOne("SELECT activa FROM marcas WHERE id = :id", array(':id' => $id));
        if (!$marca) {
            $this->json(array('success' => false, 'message' => 'No encontrada.'));
        }
        $nuevo = $marca['activa'] ? 0 : 1;
        $db->update('marcas', array('activa' => $nuevo), 'id = :id', array(':id' => $id));
        $this->json(array('success' => true, 'message' => $nuevo ? 'Marca activada.' : 'Marca desactivada.', 'nuevo_estado' => $nuevo));
    }

    // =====================================================================
    // PRODUCTOS
    // =====================================================================

    public function productosIndex() {
        $db      = Database::getInstance();
        $filtros = $this->leerFiltrosProductos();
        list($sqlBase, $params) = $this->armarConsultaProductos($filtros);

        // Paginacion en el servidor: el maestro puede manejar hasta 15.000 productos
        $porPagina    = 50;
        $fila         = $db->fetchOne("SELECT COUNT(*) as total " . $sqlBase, $params);
        $total        = (int)$fila['total'];
        $totalPaginas = max(1, (int)ceil($total / $porPagina));
        $pagina       = max(1, min($totalPaginas, intval(isset($_GET['pagina']) ? $_GET['pagina'] : 1)));
        $desde        = ($pagina - 1) * $porPagina;

        $ordenes = array(
            'nombre'      => 'p.nombre ASC',
            'precio_asc'  => 'p.precio_sugerido ASC, p.nombre ASC',
            'precio_desc' => 'p.precio_sugerido DESC, p.nombre ASC',
            'recientes'   => 'p.id DESC'
        );
        $orden = isset($ordenes[$filtros['orden']]) ? $ordenes[$filtros['orden']] : $ordenes['nombre'];

        $productos = $db->fetchAll(
            "SELECT p.*, v.nombre as vertical_nombre, m.nombre as marca_nombre,
                    (SELECT s.id FROM servicios s WHERE s.producto_id = p.id LIMIT 1) as servicio_id " . $sqlBase .
            " ORDER BY {$orden} LIMIT {$desde}, {$porPagina}",
            $params
        );

        if (SessionHelper::isAdmin()) {
            $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");
        } else {
            $verticales = $db->fetchAll(
                "SELECT id, nombre FROM verticales WHERE id = :vid AND activa = 1",
                array(':vid' => (int)SessionHelper::getVerticalId())
            );
            $marcas = AlcanceHelper::marcas((int)SessionHelper::getFranquiciadoId());
        }

        $this->render('catalogo/productos', array(
            'title'        => SessionHelper::isAdmin() ? 'Catalogo de Productos' : 'Mis Productos',
            'productos'    => $productos,
            'verticales'   => $verticales,
            'marcas'       => $marcas,
            'filtros'      => $filtros,
            'total'        => $total,
            'pagina'       => $pagina,
            'totalPaginas' => $totalPaginas,
            'porPagina'    => $porPagina,
            'desde'        => $desde
        ));
    }

    /** Lee los filtros del maestro de productos desde la query string. */
    private function leerFiltrosProductos() {
        $estado = isset($_GET['estado']) ? $_GET['estado'] : 'activos';
        if (!in_array($estado, array('activos', 'inactivos', 'todos'))) $estado = 'activos';
        // Los franquiciados solo ven sus productos activos
        if (!SessionHelper::isAdmin()) $estado = 'activos';

        $moneda = isset($_GET['moneda']) ? strtoupper(trim($_GET['moneda'])) : '';
        if (!in_array($moneda, array('CLP', 'UF'))) $moneda = '';

        $suscripcion = (isset($_GET['suscripcion']) && in_array($_GET['suscripcion'], array('1', '0'), true))
            ? $_GET['suscripcion'] : '';

        $clase = isset($_GET['clase']) ? $_GET['clase'] : '';
        if (!in_array($clase, array('producto', 'servicio'))) $clase = '';

        return array(
            'q'           => trim(isset($_GET['q']) ? $_GET['q'] : ''),
            'vertical_id' => intval(isset($_GET['vertical_id']) ? $_GET['vertical_id'] : 0),
            'marca_id'    => intval(isset($_GET['marca_id']) ? $_GET['marca_id'] : 0),
            'moneda'      => $moneda,
            'suscripcion' => $suscripcion,
            'clase'       => $clase,
            'estado'      => $estado,
            'orden'       => isset($_GET['orden']) ? $_GET['orden'] : 'nombre'
        );
    }

    /**
     * Arma el FROM/WHERE del maestro segun los filtros y el alcance del usuario.
     * Devuelve array(sql, parametros). Placeholders unicos (EMULATE_PREPARES esta apagado).
     */
    private function armarConsultaProductos($filtros) {
        $sql = "FROM productos p
                INNER JOIN verticales v ON p.vertical_id = v.id
                INNER JOIN marcas m ON p.marca_id = m.id
                WHERE 1=1";
        $params = array();

        if (!SessionHelper::isAdmin()) {
            // Franquiciado: SOLO los productos que puede VENDER (marca completa
            // autorizada o producto suelto en franquiciado_productos)
            $fid = (int)SessionHelper::getFranquiciadoId();
            $sql .= " AND (p.marca_id IN (SELECT marca_id FROM franquiciado_marcas WHERE franquiciado_id = :fid)
                       OR p.id IN (SELECT producto_id FROM franquiciado_productos WHERE franquiciado_id = :fid2))";
            $params[':fid']  = $fid;
            $params[':fid2'] = $fid;
        }

        if ($filtros['estado'] === 'activos') {
            $sql .= " AND p.activo = 1";
        } elseif ($filtros['estado'] === 'inactivos') {
            $sql .= " AND p.activo = 0";
        }
        if ($filtros['vertical_id']) {
            $sql .= " AND p.vertical_id = :filtro_vertical";
            $params[':filtro_vertical'] = $filtros['vertical_id'];
        }
        if ($filtros['marca_id']) {
            $sql .= " AND p.marca_id = :filtro_marca";
            $params[':filtro_marca'] = $filtros['marca_id'];
        }
        if ($filtros['moneda'] !== '') {
            $sql .= " AND p.moneda = :filtro_moneda";
            $params[':filtro_moneda'] = $filtros['moneda'];
        }
        if ($filtros['suscripcion'] !== '') {
            $sql .= " AND p.es_suscripcion = " . ($filtros['suscripcion'] === '1' ? '1' : '0');
        }
        if (isset($filtros['clase']) && $filtros['clase'] !== '') {
            $sql .= " AND p.tipo = " . ($filtros['clase'] === 'servicio' ? "'servicio'" : "'producto'");
        }
        if ($filtros['q'] !== '') {
            $sql .= " AND (p.nombre LIKE :q1 OR p.descripcion LIKE :q2 OR m.nombre LIKE :q3)";
            $like = '%' . $filtros['q'] . '%';
            $params[':q1'] = $like;
            $params[':q2'] = $like;
            $params[':q3'] = $like;
        }
        return array($sql, $params);
    }

    public function productosCrear() {
        if (!$this->isAdmin()) {
            $this->json(array('success' => false, 'message' => 'Sin permisos.'), 403);
        }
        $datos = $this->leerCamposProducto();
        if (!$datos['vertical_id'] || !$datos['marca_id'] || $datos['nombre'] === '') {
            $this->json(array('success' => false, 'message' => 'Vertical, marca y nombre son requeridos.'));
        }
        if ($datos['tipo'] === 'producto') {
            $this->validarPreciosProducto($datos);
        }

        $db = Database::getInstance();
        $datos['activo'] = 1;
        $id = $db->insert('productos', $datos);

        // Si es SERVICIO, se crea su ficha de configuración (planes/documento)
        $servicioId = null;
        if ($datos['tipo'] === 'servicio') {
            $servicioId = $this->sincronizarServicioDeProducto($id, $datos, 1);
            AuditHelper::log('crear_producto_servicio', 'productos', $id, null,
                array('nombre' => $datos['nombre'], 'servicio_id' => $servicioId));
        }
        $this->json(array('success' => true,
            'message' => $datos['tipo'] === 'servicio' ? 'Servicio creado. Configure ahora sus planes.' : 'Producto creado correctamente.',
            'id' => $id, 'servicio_id' => $servicioId));
    }

    /**
     * Mantiene la ficha `servicios` espejada con su producto tipo servicio:
     * reactiva o crea la fila (nombre/marca/descripción sincronizados).
     * Devuelve el id del servicio.
     */
    private function sincronizarServicioDeProducto($productoId, $datos, $activo) {
        $db = Database::getInstance();
        $servicio = $db->fetchOne("SELECT id FROM servicios WHERE producto_id = :pid", array('pid' => $productoId));
        $campos = array(
            'marca_id'    => $datos['marca_id'],
            'nombre'      => $datos['nombre'],
            'descripcion' => $datos['descripcion'],
            'activo'      => $activo ? 1 : 0
        );
        if ($servicio) {
            $db->update('servicios', $campos, 'id = :id', array(':id' => $servicio['id']));
            return (int)$servicio['id'];
        }
        $campos['producto_id'] = $productoId;
        $campos = array_merge($campos, $this->brandingDefaultsCreacion());
        $servicioId = (int)$db->insert('servicios', $campos);
        $this->sembrarDefaultsServicio($servicioId);
        return $servicioId;
    }

    /**
     * Contenido con el que nace un servicio NUEVO: texto placeholder en los
     * campos de la OFERTA, para que sea obvio en la vista previa qué sección
     * es cada una y qué falta reemplazar por contenido real. La identidad
     * corporativa (colores, logo, transferencia, condiciones, diseño) NO se
     * siembra aquí: vive en la MARCA (decisión 22-07).
     */
    private function brandingDefaultsCreacion() {
        return array(
            'estilo_bordes'    => 'formal',
            'rotulo_cliente'   => 'su empresa',
            'frase_destacada'  => 'Frase destacada: resume aquí el valor de tu cotización.',
            'titulo_bonos'     => 'Beneficios exclusivos por decidir ahora',
            'parrafos_presentacion' => json_encode(array(
                'Primer párrafo de presentación: cuéntale al cliente el contexto y el motivo de esta cotización. (Editar en Documento/Branding → Párrafos de presentación).',
                'Segundo párrafo: detalla aquí el valor concreto que tu servicio le aporta al cliente.'
            ), JSON_UNESCAPED_UNICODE),
            'garantia_texto' => json_encode(array(
                'cita' => 'Cita de la garantía: escribe aquí la promesa que respalda tu servicio.',
                'condiciones' => array('Primera condición de la garantía (editar).', 'Segunda condición de la garantía (editar).')
            ), JSON_UNESCAPED_UNICODE),
        );
    }

    /**
     * Deja el servicio recién creado con 3 planes / 3 funcionalidades por plan
     * / 3 bonos de EJEMPLO (nombres genéricos "Plan 1/2/3", "Funcionalidad
     * 1/2/3", "Bono 1/2/3") para que la matriz tenga algo que editar en vez de
     * partir de cero, y para que sea obvio en la vista previa qué falta
     * reemplazar por contenido real.
     */
    private function sembrarDefaultsServicio($servicioId) {
        $db = Database::getInstance();
        $preciosSetup    = array(9.9, 19.9, 29.9);
        $preciosMensual  = array(0.9, 1.9, 2.9);
        for ($i = 1; $i <= 3; $i++) {
            $planId = $db->insert('servicio_planes', array(
                'servicio_id'           => $servicioId,
                'codigo'                => 'plan_' . $i,
                'nombre'                => 'Plan ' . $i,
                'moneda'                => 'UF',
                'precio_inicial'        => $preciosSetup[$i - 1],
                'precio_mensual'        => $preciosMensual[$i - 1],
                'precio_inicial_minimo' => null,
                'precio_mensual_minimo' => null,
                'precio_inicial_maximo' => null,
                'precio_mensual_maximo' => null,
                'orden'                 => $i,
                'activo'                => 1
            ));
            for ($f = 1; $f <= 3; $f++) {
                $db->insert('servicio_plan_caracteristicas', array(
                    'plan_id'     => $planId,
                    'titulo'      => 'Funcionalidad ' . $f,
                    'descripcion' => '',
                    'orden'       => $f,
                    'activo'      => 1
                ));
            }
        }
        for ($b = 1; $b <= 3; $b++) {
            $db->insert('servicio_bonos', array(
                'servicio_id'          => $servicioId,
                'titulo'               => 'Bono ' . $b,
                'descripcion'          => '',
                'incluido_por_defecto' => 1,
                'orden'                => $b,
                'activo'               => 1
            ));
        }
    }

    /** Lee y normaliza los campos del formulario de producto. */
    private function leerCamposProducto() {
        $tipo = (isset($_POST['tipo']) && $_POST['tipo'] === 'servicio') ? 'servicio' : 'producto';
        $esSuscripcion = !empty($_POST['es_suscripcion']);
        $moneda  = (isset($_POST['moneda']) && $_POST['moneda'] === 'UF') ? 'UF' : 'CLP';
        $periodo = isset($_POST['periodo_suscripcion']) ? $_POST['periodo_suscripcion'] : null;
        if (!in_array($periodo, array('mensual', 'trimestral', 'semestral', 'anual'))) $periodo = null;
        $maximo  = (isset($_POST['precio_maximo']) && $_POST['precio_maximo'] !== '')
            ? floatval(str_replace(',', '.', $_POST['precio_maximo'])) : null;

        $datos = array(
            'vertical_id'         => intval(isset($_POST['vertical_id']) ? $_POST['vertical_id'] : 0),
            'marca_id'            => intval(isset($_POST['marca_id']) ? $_POST['marca_id'] : 0),
            'nombre'              => trim(isset($_POST['nombre']) ? $_POST['nombre'] : ''),
            'descripcion'         => trim(isset($_POST['descripcion']) ? $_POST['descripcion'] : ''),
            'tipo'                => $tipo,
            'precio_minimo'       => floatval(str_replace(',', '.', isset($_POST['precio_minimo']) ? $_POST['precio_minimo'] : 0)),
            'precio_maximo'       => $maximo,
            'precio_sugerido'     => floatval(str_replace(',', '.', isset($_POST['precio_sugerido']) ? $_POST['precio_sugerido'] : 0)),
            'moneda'              => $moneda,
            'es_suscripcion'      => $esSuscripcion ? 1 : 0,
            'periodo_suscripcion' => $esSuscripcion ? $periodo : null
        );

        // Los SERVICIOS no llevan banda ni suscripción: sus precios viven en
        // los planes (servicio_planes).
        if ($tipo === 'servicio') {
            $datos['precio_minimo']       = 0;
            $datos['precio_maximo']       = null;
            $datos['precio_sugerido']     = 0;
            $datos['moneda']              = 'CLP';
            $datos['es_suscripcion']      = 0;
            $datos['periodo_suscripcion'] = null;
        }
        // La identidad del documento ya NO es por producto: vive en la MARCA
        // (marcas.branding_json, decisión 22-07). branding_json queda intacto.
        return $datos;
    }

    /** Valida la coherencia de la banda de precios y la suscripcion. */
    private function validarPreciosProducto($datos) {
        if ($datos['precio_maximo'] !== null && $datos['precio_maximo'] > 0
            && $datos['precio_maximo'] < $datos['precio_minimo']) {
            $this->json(array('success' => false, 'message' => 'El precio máximo no puede ser menor que el precio mínimo.'));
        }
        if ($datos['precio_sugerido'] > 0) {
            if ($datos['precio_sugerido'] < $datos['precio_minimo']) {
                $this->json(array('success' => false, 'message' => 'El precio sugerido no puede quedar bajo el mínimo.'));
            }
            if ($datos['precio_maximo'] !== null && $datos['precio_maximo'] > 0 && $datos['precio_sugerido'] > $datos['precio_maximo']) {
                $this->json(array('success' => false, 'message' => 'El precio sugerido no puede superar el máximo.'));
            }
        }
        if ($datos['es_suscripcion'] && !$datos['periodo_suscripcion']) {
            $this->json(array('success' => false, 'message' => 'Si el producto es suscripción, debes elegir el período (mensual, trimestral, semestral o anual).'));
        }
    }

    public function productosEditar() {
        if (!$this->isAdmin()) {
            $this->json(array('success' => false, 'message' => 'Sin permisos.'), 403);
        }
        $id    = intval(isset($_POST['id']) ? $_POST['id'] : 0);
        $datos = $this->leerCamposProducto();
        if (!$id || !$datos['vertical_id'] || !$datos['marca_id'] || $datos['nombre'] === '') {
            $this->json(array('success' => false, 'message' => 'Datos invalidos.'));
        }
        if ($datos['tipo'] === 'producto') {
            $this->validarPreciosProducto($datos);
        }

        $db = Database::getInstance();
        $anterior = $db->fetchOne("SELECT tipo, activo, nombre FROM productos WHERE id = :id", array(':id' => $id));
        if (!$anterior) {
            $this->json(array('success' => false, 'message' => 'Producto no encontrado.'));
        }
        $db->update('productos', $datos, 'id = :id', array(':id' => $id));

        // Ciclo de vida de la ficha `servicios` espejo:
        //   servicio          → sincronizar nombre/marca/descripción (o crearla)
        //   servicio→producto → la ficha se DESACTIVA, jamás se borra
        //                       (cotizaciones históricas la siguen leyendo)
        $servicioId = null;
        if ($datos['tipo'] === 'servicio') {
            $servicioId = $this->sincronizarServicioDeProducto($id, $datos, (int)$anterior['activo']);
        } elseif ($anterior['tipo'] === 'servicio') {
            $servicio = $db->fetchOne("SELECT id FROM servicios WHERE producto_id = :pid", array('pid' => $id));
            if ($servicio) {
                $db->update('servicios', array('activo' => 0), 'id = :id', array(':id' => $servicio['id']));
            }
        }
        if ($anterior['tipo'] !== $datos['tipo']) {
            AuditHelper::log('cambiar_tipo_producto', 'productos', $id,
                array('tipo' => $anterior['tipo']), array('tipo' => $datos['tipo'], 'servicio_id' => $servicioId));
        }
        $this->json(array('success' => true, 'message' => 'Producto actualizado.', 'servicio_id' => $servicioId));
    }

    public function productosToggle() {
        if (!$this->isAdmin()) {
            $this->json(array('success' => false, 'message' => 'Sin permisos.'), 403);
        }
        $id = intval(isset($_POST['id']) ? $_POST['id'] : 0);
        if (!$id) {
            $this->json(array('success' => false, 'message' => 'ID invalido.'));
        }
        $db   = Database::getInstance();
        $prod = $db->fetchOne("SELECT activo, tipo FROM productos WHERE id = :id", array(':id' => $id));
        if (!$prod) {
            $this->json(array('success' => false, 'message' => 'No encontrado.'));
        }
        $nuevo = $prod['activo'] ? 0 : 1;
        $db->update('productos', array('activo' => $nuevo), 'id = :id', array(':id' => $id));
        // El activo de un producto-servicio se espeja en su ficha de servicio
        if ($prod['tipo'] === 'servicio') {
            $db->update('servicios', array('activo' => $nuevo), 'producto_id = :pid', array(':pid' => $id));
        }
        $this->json(array('success' => true, 'message' => $nuevo ? 'Producto activado.' : 'Producto desactivado.', 'nuevo_estado' => $nuevo));
    }

    // =====================================================================
    // MAESTRO: EXPORTAR / PLANTILLA / CARGA MASIVA
    // =====================================================================

    /** Descarga en CSV (compatible con Excel) los productos con los filtros aplicados. */
    public function productosExportar() {
        $db      = Database::getInstance();
        $filtros = $this->leerFiltrosProductos();
        list($sqlBase, $params) = $this->armarConsultaProductos($filtros);

        $productos = $db->fetchAll(
            "SELECT p.*, v.nombre as vertical_nombre, m.nombre as marca_nombre " . $sqlBase .
            " ORDER BY v.nombre ASC, m.nombre ASC, p.nombre ASC",
            $params
        );

        AuditHelper::log('exportar', 'productos', null, null, array('filas' => count($productos), 'filtros' => $filtros));

        header('Content-Type: text/csv; charset=UTF-8');
        header('Content-Disposition: attachment; filename="productos_' . date('Y-m-d') . '.csv"');
        header('Pragma: no-cache');

        $salida = fopen('php://output', 'w');
        // BOM para que Excel abra el archivo directamente en UTF-8 (tildes correctas)
        fwrite($salida, "\xEF\xBB\xBF");
        fputcsv($salida, $this->columnasCsvProductos(), ';');
        foreach ($productos as $p) {
            fputcsv($salida, array(
                $p['id'],
                $p['vertical_nombre'],
                $p['marca_nombre'],
                $p['nombre'],
                isset($p['descripcion']) ? $p['descripcion'] : '',
                isset($p['tipo']) ? $p['tipo'] : 'producto',
                $p['moneda'],
                $this->numeroParaCsv($p['precio_minimo']),
                ($p['precio_maximo'] !== null && $p['precio_maximo'] !== '') ? $this->numeroParaCsv($p['precio_maximo']) : '',
                $this->numeroParaCsv($p['precio_sugerido']),
                !empty($p['es_suscripcion']) ? 'SI' : 'NO',
                (!empty($p['es_suscripcion']) && !empty($p['periodo_suscripcion'])) ? $p['periodo_suscripcion'] : '',
                !empty($p['activo']) ? 'SI' : 'NO'
            ), ';');
        }
        fclose($salida);
        exit;
    }

    /** Descarga la plantilla CSV para la carga masiva (con filas de ejemplo). */
    public function productosPlantilla() {
        if (!$this->isAdmin()) {
            $this->json(array('success' => false, 'message' => 'Sin permisos.'), 403);
        }
        header('Content-Type: text/csv; charset=UTF-8');
        header('Content-Disposition: attachment; filename="plantilla_productos.csv"');
        header('Pragma: no-cache');

        $salida = fopen('php://output', 'w');
        fwrite($salida, "\xEF\xBB\xBF");
        fputcsv($salida, $this->columnasCsvProductos(), ';');
        fputcsv($salida, array('', 'Farmacias', 'Control Farma', 'PLAN BASICO EJEMPLO', 'Licencia mensual del plan basico', 'producto', 'CLP', '10000', '50000', '25000', 'NO', '', 'SI'), ';');
        fputcsv($salida, array('', 'Farmacias', 'Control Farma', 'PLAN PREMIUM EJEMPLO', 'Suscripcion anual premium', 'producto', 'UF', '5', '12', '8,5', 'SI', 'anual', 'SI'), ';');
        fclose($salida);
        exit;
    }

    /**
     * Carga masiva desde CSV. Si la fila trae id se actualiza ese producto;
     * si no, se busca por nombre + marca (existe = actualizar, no existe = crear).
     * Las verticales y marcas que no existan se crean automaticamente.
     */
    public function productosImportar() {
        if (!$this->isAdmin()) {
            $this->json(array('success' => false, 'message' => 'Sin permisos.'), 403);
        }
        if (empty($_FILES['archivo']) || !is_uploaded_file($_FILES['archivo']['tmp_name'])) {
            $this->json(array('success' => false, 'message' => 'Debes adjuntar el archivo CSV con los productos.'));
        }
        if ($_FILES['archivo']['error'] !== UPLOAD_ERR_OK) {
            $this->json(array('success' => false, 'message' => 'El archivo no se subio completo (codigo ' . $_FILES['archivo']['error'] . '). Intenta de nuevo.'));
        }
        if ($_FILES['archivo']['size'] > 8 * 1024 * 1024) {
            $this->json(array('success' => false, 'message' => 'El archivo supera los 8 MB. Divide la carga en partes mas chicas.'));
        }

        $contenido = file_get_contents($_FILES['archivo']['tmp_name']);
        if (substr($contenido, 0, 3) === "\xEF\xBB\xBF") $contenido = substr($contenido, 3);
        // Excel en Windows suele guardar el CSV en ANSI; se convierte a UTF-8
        if (!mb_check_encoding($contenido, 'UTF-8')) {
            $contenido = mb_convert_encoding($contenido, 'UTF-8', 'Windows-1252');
        }

        $flujo = fopen('php://temp', 'r+');
        fwrite($flujo, $contenido);
        rewind($flujo);

        $primeraLinea = fgets($flujo);
        if ($primeraLinea === false || trim($primeraLinea) === '') {
            $this->json(array('success' => false, 'message' => 'El archivo esta vacio.'));
        }
        $separador = (substr_count($primeraLinea, ';') >= substr_count($primeraLinea, ',')) ? ';' : ',';
        rewind($flujo);

        // Mapa de columnas: acepta variantes de nombre de encabezado
        $alias = array(
            'id' => 'id', 'vertical' => 'vertical', 'marca' => 'marca',
            'nombre' => 'nombre', 'producto' => 'nombre', 'descripcion' => 'descripcion',
            'moneda' => 'moneda',
            'preciominimo' => 'precio_minimo', 'minimo' => 'precio_minimo',
            'preciomaximo' => 'precio_maximo', 'maximo' => 'precio_maximo',
            'preciosugerido' => 'precio_sugerido', 'sugerido' => 'precio_sugerido', 'precio' => 'precio_sugerido',
            'essuscripcion' => 'es_suscripcion', 'suscripcion' => 'es_suscripcion',
            'periodosuscripcion' => 'periodo', 'periodo' => 'periodo',
            'activo' => 'activo', 'estado' => 'activo',
            'tipo' => 'tipo', 'clase' => 'tipo'
        );
        $encabezados = fgetcsv($flujo, 0, $separador);
        $mapa = array();
        foreach ($encabezados as $indice => $encabezado) {
            $clave = $this->normalizarEncabezado($encabezado);
            if (isset($alias[$clave])) $mapa[$alias[$clave]] = $indice;
        }
        foreach (array('vertical', 'marca', 'nombre') as $requerida) {
            if (!isset($mapa[$requerida])) {
                $this->json(array('success' => false, 'message' => "Falta la columna '{$requerida}' en el encabezado. Descarga la plantilla para ver el formato (columnas: " . implode(', ', $this->columnasCsvProductos()) . ').'));
            }
        }

        $db = Database::getInstance();

        // Cache de verticales y marcas para no consultar por cada fila
        $verticalesCache = array();
        foreach ($db->fetchAll("SELECT id, nombre FROM verticales") as $v) {
            $verticalesCache[mb_strtolower(trim($v['nombre']), 'UTF-8')] = (int)$v['id'];
        }
        $marcasCache = array();
        foreach ($db->fetchAll("SELECT id, vertical_id, nombre FROM marcas") as $m) {
            $marcasCache[$m['vertical_id'] . '|' . mb_strtolower(trim($m['nombre']), 'UTF-8')] = (int)$m['id'];
        }

        $periodosValidos = array('mensual', 'trimestral', 'semestral', 'anual');
        $insertados = 0;
        $actualizados = 0;
        $verticalesCreadas = 0;
        $marcasCreadas = 0;
        $errores = array();
        $linea = 1;
        $procesadas = 0;

        $db->beginTransaction();
        try {
            while (($fila = fgetcsv($flujo, 0, $separador)) !== false) {
                $linea++;
                if (count(array_filter($fila, 'strlen')) === 0) continue; // fila en blanco
                $procesadas++;
                if ($procesadas > 20000) {
                    $errores[] = "Se alcanzo el tope de 20.000 filas por carga; el resto no se proceso.";
                    break;
                }

                $id          = intval($this->celdaCsv($fila, $mapa, 'id'));
                $vertical    = trim($this->celdaCsv($fila, $mapa, 'vertical'));
                $marca       = trim($this->celdaCsv($fila, $mapa, 'marca'));
                $nombre      = trim($this->celdaCsv($fila, $mapa, 'nombre'));
                $descripcion = trim($this->celdaCsv($fila, $mapa, 'descripcion'));
                $moneda      = strtoupper(trim($this->celdaCsv($fila, $mapa, 'moneda')));
                $minimo      = $this->numeroDesdeCsv($this->celdaCsv($fila, $mapa, 'precio_minimo'));
                $textoMaximo = trim($this->celdaCsv($fila, $mapa, 'precio_maximo'));
                $maximo      = ($textoMaximo === '') ? null : $this->numeroDesdeCsv($textoMaximo);
                $sugerido    = $this->numeroDesdeCsv($this->celdaCsv($fila, $mapa, 'precio_sugerido'));
                $esSuscripcion = $this->siNoDesdeCsv($this->celdaCsv($fila, $mapa, 'es_suscripcion'), 0);
                $periodo     = mb_strtolower(trim($this->celdaCsv($fila, $mapa, 'periodo')), 'UTF-8');
                $activo      = $this->siNoDesdeCsv($this->celdaCsv($fila, $mapa, 'activo'), 1);

                if ($nombre === '') { $errores[] = "Linea {$linea}: falta el nombre del producto."; continue; }
                if ($vertical === '') { $errores[] = "Linea {$linea}: falta la vertical."; continue; }
                if ($marca === '') { $errores[] = "Linea {$linea}: falta la marca."; continue; }
                // Los SERVICIOS no viajan por CSV (sus planes no van en el archivo
                // y un upsert masivo dejaría la ficha de servicio desincronizada)
                $tipoCsv = mb_strtolower(trim($this->celdaCsv($fila, $mapa, 'tipo')), 'UTF-8');
                if ($tipoCsv === 'servicio') {
                    $errores[] = "Linea {$linea}: los servicios se crean y editan desde la pantalla de Productos (sus planes no viajan por CSV).";
                    continue;
                }
                if ($moneda === '') $moneda = 'CLP';
                if (!in_array($moneda, array('CLP', 'UF'))) { $errores[] = "Linea {$linea}: moneda '{$moneda}' invalida (usa CLP o UF)."; continue; }
                if ($minimo === null) { $errores[] = "Linea {$linea}: precio minimo invalido."; continue; }
                if ($textoMaximo !== '' && $maximo === null) { $errores[] = "Linea {$linea}: precio maximo invalido."; continue; }
                if ($sugerido === null) { $errores[] = "Linea {$linea}: precio sugerido invalido."; continue; }
                if ($maximo !== null && $maximo > 0 && $maximo < $minimo) { $errores[] = "Linea {$linea}: el maximo ({$maximo}) es menor que el minimo ({$minimo})."; continue; }
                if ($sugerido > 0 && $sugerido < $minimo) { $errores[] = "Linea {$linea}: el sugerido ({$sugerido}) queda bajo el minimo ({$minimo})."; continue; }
                if ($sugerido > 0 && $maximo !== null && $maximo > 0 && $sugerido > $maximo) { $errores[] = "Linea {$linea}: el sugerido ({$sugerido}) supera el maximo ({$maximo})."; continue; }
                if ($esSuscripcion && !in_array($periodo, $periodosValidos)) { $errores[] = "Linea {$linea}: producto suscripcion sin periodo valido (mensual, trimestral, semestral o anual)."; continue; }

                // Vertical y marca: se buscan por nombre y se crean si no existen
                $claveVertical = mb_strtolower($vertical, 'UTF-8');
                if (!isset($verticalesCache[$claveVertical])) {
                    $verticalesCache[$claveVertical] = (int)$db->insert('verticales', array(
                        'nombre' => $vertical, 'descripcion' => '', 'activa' => 1
                    ));
                    $verticalesCreadas++;
                }
                $verticalId = $verticalesCache[$claveVertical];

                $claveMarca = $verticalId . '|' . mb_strtolower($marca, 'UTF-8');
                if (!isset($marcasCache[$claveMarca])) {
                    $marcasCache[$claveMarca] = (int)$db->insert('marcas', array(
                        'vertical_id' => $verticalId, 'nombre' => $marca,
                        'contacto_nombre' => '', 'contacto_email' => '', 'contacto_telefono' => '',
                        'activa' => 1
                    ));
                    $marcasCreadas++;
                }
                $marcaId = $marcasCache[$claveMarca];

                $datos = array(
                    'vertical_id'         => $verticalId,
                    'marca_id'            => $marcaId,
                    'nombre'              => $nombre,
                    'descripcion'         => $descripcion,
                    'precio_minimo'       => $minimo,
                    'precio_maximo'       => ($maximo !== null && $maximo > 0) ? $maximo : null,
                    'precio_sugerido'     => $sugerido,
                    'moneda'              => $moneda,
                    'es_suscripcion'      => $esSuscripcion,
                    'periodo_suscripcion' => $esSuscripcion ? $periodo : null,
                    'activo'              => $activo
                );

                if ($id > 0) {
                    $existe = $db->fetchOne("SELECT id, tipo FROM productos WHERE id = :id", array(':id' => $id));
                    if (!$existe) { $errores[] = "Linea {$linea}: no existe un producto con id {$id}."; continue; }
                    if ($existe['tipo'] === 'servicio') { $errores[] = "Linea {$linea}: el producto {$id} es un SERVICIO y no se modifica por CSV."; continue; }
                    $db->update('productos', $datos, 'id = :id', array(':id' => $id));
                    $actualizados++;
                } else {
                    $existente = $db->fetchOne(
                        "SELECT id, tipo FROM productos WHERE marca_id = :marca_id AND nombre = :nombre",
                        array(':marca_id' => $marcaId, ':nombre' => $nombre)
                    );
                    if ($existente) {
                        if ($existente['tipo'] === 'servicio') { $errores[] = "Linea {$linea}: '{$nombre}' es un SERVICIO y no se modifica por CSV."; continue; }
                        $db->update('productos', $datos, 'id = :id', array(':id' => (int)$existente['id']));
                        $actualizados++;
                    } else {
                        $db->insert('productos', $datos);
                        $insertados++;
                    }
                }
            }
            $db->commit();
        } catch (Exception $e) {
            $db->rollBack();
            $this->json(array('success' => false, 'message' => 'La carga fallo y no se guardo nada: ' . $e->getMessage()));
        }
        fclose($flujo);

        $resumen = array(
            'archivo'            => $_FILES['archivo']['name'],
            'procesadas'         => $procesadas,
            'insertados'         => $insertados,
            'actualizados'       => $actualizados,
            'verticales_creadas' => $verticalesCreadas,
            'marcas_creadas'     => $marcasCreadas,
            'errores'            => count($errores)
        );
        AuditHelper::log('carga_masiva', 'productos', null, null, $resumen);

        $this->json(array(
            'success'            => true,
            'message'            => "Carga terminada: {$insertados} creados, {$actualizados} actualizados" . (count($errores) ? ', ' . count($errores) . ' con error' : '') . '.',
            'insertados'         => $insertados,
            'actualizados'       => $actualizados,
            'verticales_creadas' => $verticalesCreadas,
            'marcas_creadas'     => $marcasCreadas,
            'procesadas'         => $procesadas,
            'errores'            => array_slice($errores, 0, 30),
            'errores_total'      => count($errores)
        ));
    }

    /** Columnas oficiales del CSV del maestro (export, plantilla e import usan las mismas). */
    private function columnasCsvProductos() {
        return array('id', 'vertical', 'marca', 'nombre', 'descripcion', 'tipo', 'moneda',
            'precio_minimo', 'precio_maximo', 'precio_sugerido',
            'es_suscripcion', 'periodo_suscripcion', 'activo');
    }

    /** Normaliza un encabezado de CSV: minusculas, sin tildes, espacios ni guiones. */
    private function normalizarEncabezado($texto) {
        $texto = mb_strtolower(trim($texto), 'UTF-8');
        $texto = str_replace(
            array('á', 'é', 'í', 'ó', 'ú', 'ñ'),
            array('a', 'e', 'i', 'o', 'u', 'n'),
            $texto
        );
        return preg_replace('/[^a-z0-9]/', '', $texto);
    }

    /** Devuelve la celda de una columna mapeada (cadena vacia si no viene). */
    private function celdaCsv($fila, $mapa, $columna) {
        if (!isset($mapa[$columna])) return '';
        return isset($fila[$mapa[$columna]]) ? $fila[$mapa[$columna]] : '';
    }

    /**
     * Convierte un numero de CSV a float aceptando formato chileno y anglo:
     * "1.234,56", "1234.56", "$ 10.000", "12,5". Vacio = 0. Invalido = null.
     */
    private function numeroDesdeCsv($texto) {
        $texto = trim(str_replace(array('$', ' ', "\xc2\xa0"), '', $texto));
        if ($texto === '') return 0.0;
        $tienePunto = strpos($texto, '.') !== false;
        $tieneComa  = strpos($texto, ',') !== false;
        if ($tienePunto && $tieneComa) {
            // El ultimo separador es el decimal; el otro es de miles
            if (strrpos($texto, ',') > strrpos($texto, '.')) {
                $texto = str_replace('.', '', $texto);
                $texto = str_replace(',', '.', $texto);
            } else {
                $texto = str_replace(',', '', $texto);
            }
        } elseif ($tieneComa) {
            $texto = str_replace(',', '.', $texto);
        } elseif ($tienePunto) {
            // Un punto con exactamente 3 digitos despues y sin mas puntos = miles ("10.000")
            if (preg_match('/^\d{1,3}(\.\d{3})+$/', $texto)) {
                $texto = str_replace('.', '', $texto);
            }
        }
        if (!is_numeric($texto)) return null;
        $valor = (float)$texto;
        return ($valor < 0) ? null : $valor;
    }

    /** Interpreta SI/NO (y variantes 1/0, true/false) de una celda del CSV. */
    private function siNoDesdeCsv($texto, $porDefecto) {
        $texto = mb_strtolower(trim($texto), 'UTF-8');
        if ($texto === '') return $porDefecto;
        if (in_array($texto, array('si', 'sí', 's', '1', 'true', 'verdadero', 'x'))) return 1;
        if (in_array($texto, array('no', 'n', '0', 'false', 'falso'))) return 0;
        return $porDefecto;
    }

    /** Formatea un numero para el CSV: enteros sin decimales, decimales con coma. */
    private function numeroParaCsv($valor) {
        $valor = (float)$valor;
        if (abs($valor - floor($valor)) < 0.005) {
            return number_format($valor, 0, '', '');
        }
        return number_format($valor, 2, ',', '');
    }
}
