/* 
* Copyright 2016 Agencia Estatal de Administración Tibutaria
*
* Licencia con arreglo a la EUPL, Versión 1.1 o –en cuanto
* sean aprobadas por la Comisión Europea– versiones
* posteriores de la EUPL (la «Licencia»);
* Solo podrá usarse esta obra si se respeta la Licencia.
* Puede obtenerse una copia de la Licencia en:
*  
* https://joinup.ec.europa.eu/software/page/eupl
*  
* Salvo cuando lo exija la legislación aplicable o se acuerde
* por escrito, el programa distribuido con arreglo a la
* Licencia se distribuye «TAL CUAL», SIN GARANTÍAS NI CONDICIONES
* DE NINGÚN TIPO, ni expresasni implícitas.
*
* Véase la Licencia en el idioma concreto que rige los permisos y 
* limitaciones que establece la Licencia.
*/ 

package servidor.comunicaciones;

import java.util.*;
import java.util.logging.Level;
import org.quickserver.net.server.*;
import servidor.*;
import servidor.clientes.*;
import servidor.datos.*;
import servidor.datos.base.*;
import servidor.utiles.*;

public class GestorColas {

    //<editor-fold desc="1. Constantes & Variables ">
    public final ColaMensajes colaMensajes = new ColaMensajes();
    public Operadores operadores;
    public OperadoresDesconectados operadoresDesconectados;
    public Gestores gestores;
    public Monitores monitores;
    public Administradores administradores;
    public TablaCandidatos tablaCandidatos;
    public TablaHuecos tablaHuecos;
    public CursorPresentes tablaPresentes;
    public TablaOperadores tablaOperadores;
    public TablaAcciones tablaAcciones;
    public TablaColas tablaColas;
    public TablaMensajesGestores tablaMsjGestores;
    public TablaProductos tablaProductos;
    public TablaCategoriasEstadisticas tablaCategorias;
    public TablaPuestos tablaPuestos;
    public GestorEnviosMasivos enviosMasivos;
    public Resucitador resucitador = null;
    public boolean ordenarConPrioridad;
    private int ejecucionesPrioridad = 0;
    private final EstimadorRetrasos estimador;
    private final Configuracion conf;
    private static GestorColas yo;
    private volatile int monitoresVivos = 0;
    //private int numeroMesas;
    private static final int TIMEOUT_FANTASMA = 60000;  // 10 seg. * 6 (por que sí)
    private LogGenerico logNotificaciones = null;
    private LogGenerico logConexiones = null;
    public String ultimoResumen = "";
    public ResumenColas resumen;
    static long maxRetrasoMensaje = 10000;
    private String colaCero0;

    //</editor-fold>
    //<editor-fold desc="2. Constructor + inicio">
    public GestorColas() {
        yo = this;
        tablaAcciones = new TablaAcciones();
        tablaAcciones.cargar("SELECT * FROM   AccionesHoy");
        tablaCandidatos = new TablaCandidatos();
        tablaCandidatos.cargar("SELECT * FROM Candidatos");
        tablaHuecos = new TablaHuecos();
        tablaHuecos.cargar("SELECT * FROM Huecos");
        tablaHuecos.cargarTablaRangoHuecos();
        tablaPresentes = new CursorPresentes(tablaCandidatos);
        tablaOperadores = new TablaOperadores();
        tablaOperadores.cargar("SELECT * FROM operadores WHERE activo=1");
        tablaColas = new TablaColas();
        tablaColas.cargar("SELECT Colas.*, SalasEspera.TiempoEspera,  SalasEspera.TextoSalaEspera FROM Colas "
                + "LEFT OUTER JOIN SalasEspera ON Colas.IdSalaEspera = SalasEspera.IdSalaEspera ");
        //"WHERE Colas.activa=1 ");
        conf = Configuracion.getConfiguracion();
        //  tablaColas.comprobarColaEstadisticaCERO();
        colaCero0 = "";
        if (conf.modoColaCero) {
            comprobarSiColaCero();
        }
        tablaCandidatos.tablaColas = tablaColas;
        tablaMsjGestores = new TablaMensajesGestores();
        tablaProductos = new TablaProductos();
        tablaPuestos = new TablaPuestos();
        tablaPuestos.cargarTabla();
        tablaCategorias = new TablaCategoriasEstadisticas();
        tablaCategorias.recargar();

        if (conf.maxRetrasoMensaje != 0) {
            maxRetrasoMensaje = conf.maxRetrasoMensaje;
        }
        if (conf.resucitar) {
            resucitador = new Resucitador(tablaColas);
            tablaCandidatos.restaurarResucitados(resucitador);
        }
        if (conf.prioridadCitaPrevia > 0) {
            ordenarConPrioridad = true;
        }
        operadores = new Operadores();
        operadoresDesconectados = new OperadoresDesconectados();
        gestores = new Gestores();
        monitores = new Monitores();
        administradores = new Administradores();
        enviosMasivos = new GestorEnviosMasivos();
        estimador = new EstimadorRetrasos(this);
        resumen = new ResumenColas();
        Cliente.gestor = this;
    }

    public final void comprobarSiColaCero() {
        if (conf.colaCero.length() < 1) {
            conf.modoColaCero = false;
            colaCero0 = "";
            System.out.println("No se ha definido una cola valida para ColaAutoenvio en el fichero configuracionServidor.ini");
            return;
        }
        if (tablaColas.buscarColaCeroActiva(conf.colaCero) == null) {
            System.out.println("No existe una cola Activa para  AutoenvioColaCero");
            conf.modoColaCero = false;
            conf.colaCero = "";
            colaCero0 = "";
            return;
        }
        colaCero0 = conf.colaCero.toUpperCase();
    }

    public void releerTablas() {
        if (BaseDatos.bd().recuperacionActivada) {
            System.out.println(" El sistema está desconectado del servidor SQL. No es posible recargar tablas");
            System.out.println(" Esta debe ser una situación temporal. Contacte con el Administrador de su red");
            return;
        }
        tablaOperadores.recargar();
        tablaAcciones.recargar();
        tablaColas.recargar();
        tablaCandidatos.recargar();
        tablaHuecos.recargar();
        tablaHuecos.cargarTablaRangoHuecos();
        Global.tablaPuestos.cargarTabla();
        operadores.cargarLocalizacion();
        if (gestores != null) {
            gestores.enviarTablas();
        }
        if (operadores != null) {
            operadores.enviarTablas(true);
        }
        if (administradores != null) {
            administradores.enviarTablas();
        }
        tablaCandidatos.restaurarResucitados(resucitador);
        System.out.println("Recarga de datos completada");
    }

    public static GestorColas getGestor() {
        return yo;
    }

    public void arranca() {
        ThreadLecturaColaMensajes hilo = new ThreadLecturaColaMensajes();
        hilo.start();
    }

    public String[] obtenerEstado() {
        String[] datos = new String[10];
        datos[0] = Global.modoAutomatico ? "AUTOMÁTICO" : "MANUAL";
        datos[1] = conf.comprobarMonitores ? "ACTIVADO" : "DESACTIVADO";
        datos[2] = conf.modoSinGestor ? "NO" : "SI";
        datos[3] = String.valueOf(contarPresentes());
        datos[4] = String.valueOf(operadores.cuenta());
        datos[5] = (Global.main.getTurno().equals("M") ? "Mañana" : "Tarde");
        datos[6] = String.valueOf(conf.prioridadCitaPrevia);
        datos[7] = conf.gestionHuecosActivada ? "SI" : "NO";
        datos[8] = BaseDatos.bd().pathAteneo;
        datos[9] = BaseDatos.bd().recuperacionActivada ? "RECUP SI" : "RECUP NO";
        return datos;
    }
   //</editor-fold>

    //<editor-fold desc="4. Operadores ">
    public boolean nuevoOperador(Operador operador) throws Exception {
        if (operador.getEstado() != Operador.NOCONECTADO) {
            operadores.remove(operador);
        }
        String puesto = operador.getPuesto();
        int numMesa;
        try {
            numMesa = Integer.parseInt(operador.getPuesto());
        } catch (NumberFormatException exc) {
            operador.mensajeRechazadaMesa();
            return false;
        }
        if (operadores.contiene(operador)) {
            operador.mensajeRechazadaMesa();
            return false;
        }
        if (numMesa < 1 || numMesa > 999) {
            operador.mensajeRechazadaMesa();
            return false;
        }
        String codigoOperador = operador.getNombre();
        if (operadores.codigoOperadorConectado(codigoOperador)) {
            operador.mensajeRechazadoOperador();
            return false;
        }
        RegistroOperadores regOp = tablaOperadores.buscarCodigo(codigoOperador);
        if (regOp == null) {
            operador.mensajeRechazadoOperador();
            return false;
        }

        tablaPuestos.nuevoPuesto(puesto);
        String nombreOperador = regOp.getNombre();
        if (operadoresDesconectados.contiene(codigoOperador)) {
            operador.cargaCapacidades(operadoresDesconectados.getCapacidades(codigoOperador));
            operador.comprobarCapacidadesPorMesa();
            operadoresDesconectados.eliminar(codigoOperador);
        } else {
            operador.cargaInicialCapacidades(codigoOperador);
        }
        operador.cargaLocalizacion(operador.getPuesto());
        operador.setCentro(regOp.getCentro());
        operadores.add(operador);
        if (operador.getEstado() != Operador.NOCONECTADO) {
            if (tablaAcciones.buscar(operador) == null) {
                operador.setEstado(Operador.NOCONECTADO);
            }
        }
        if (operador.getEstado() == Operador.NOCONECTADO) {
            tablaAcciones.primeraConexion(operador.getPuesto(), operador.getNombre(), operador.getCentro());
            operador.setEstado(Operador.CONECTADO);
        } else {
            if (operador.getEstado() == Operador.LIBRE) {
                operadores.meterEnLibres(operador);
                tablaAcciones.cambiaEstado(operador.getPuesto(), operador.getNombre(), 0, operador.getEstado(), "");
            } else {
                tablaAcciones.cambiaEstado(operador.getPuesto(), operador.getNombre(), 0, Operador.CONECTADO, "");
            }
        }
        operador.aceptadaConexion(nombreOperador, operador.getEstado(), regOp.getFavEstadisticas());
        gestores.enviarAltaOperador(operador.getPuesto(), nombreOperador);

        operador.enviarTablas(false);

        administradores.enviarMensaje(Administrador.MSG_CAMBIO_CAPACIDAD, operador.getNombre(), operador.capacidadesToString_2(), operador.suplenciasToString());
        RegistroAcciones reg;
        if ((reg = tablaAcciones.buscar(operador)) != null) {
            administradores.comunicarCambioEnAcciones(reg.registro());
            return true;
        } else {
            return false;
        }
    }

    public void desconectarOperador(String mesa) throws Exception {
        Operador operador = operadores.get(mesa);
        if (operador == null) {
            return;
        }
        RegistroAcciones reg = tablaAcciones.cambiaEstado(operador.getPuesto(), operador.getNombre(), 0, Operador.NOCONECTADO, "");
        operador.getHandler().closeConnection();
        operadores.remove(operador);
        System.out.println("Operador desconectado en la mesa " + mesa);
        gestores.enviarCambioEstadoMesa(mesa, Gestor.MESA_DESCONECTADA);
        administradores.comunicarCambioEnAcciones(reg.registro());
        resumen.calcularSuplencias();
    }

    public void puestoEsperando(MensajeOperador mensaje) {
        accionesCambioDeEstado(mensaje, Operador.ENTRANSITO);
    }

    public void puestoLibre(MensajeOperador mensaje) {
        Operador op = operadores.get(mensaje.getMesa());
        if (op != null) {
            try {
                operadores.meterEnLibres(op);
                int estadoAnterior = op.getEstado();
                op.setEstado(Operador.LIBRE);
                accionesCambioDeEstado(mensaje, Operador.LIBRE);
                if (mensaje.codigo == MensajeOperador.ESTADO_TRABAJO_A_LIBRE) {
                    tablaCandidatos.ponerEstadoFinalizado(mensaje.getId(), false);
                    gestores.enviarCambioEstadoCandidato(tablaCandidatos.buscarRegistroPorId(mensaje.getId()));
                    if (conf.resucitar && op.reg != null) {
                        resucitador.add(op.reg);
                    }
                } else if (estadoAnterior == Operador.ENTRANSITO) {  // mensaje: ESTADO_LIBRE ó DESCANSO_A_LIBRE:
                    if (conf.resucitar && op.reg != null) {
                        resucitador.add(op.reg);
                    }
                }
                gestores.enviarCambioEstadoMesa(op.getPuesto(), Gestor.MESA_LIBRE);
                resumen.calcularSuplencias();
            } catch (Exception exc) {
                servidor.ServidorMain.logException("ERROR al poner estado LIBRE al operador\n" + op.toString()
                        + "\n" + mensaje.toString(), exc);
            }
        }
    }

    void enviaMensajeNuevoContribuyente(Operador op, int tipoMensaje, RegistroCandidatos reg, String ticket) {
        String elTicket = ticket == null ? reg.getNumeroTicket() : ticket;
        StringBuilder sb = new StringBuilder();
        sb.append(reg.getNif()).append('|').append(reg.getNombre()).append('|').append(reg.getCitaMultiple()).append('|');
        sb.append(reg.getCola()).append('|').append(elTicket).append('|').append(reg.getId());
        sb.append('|').append(reg.getCodigoGestor());
        if (tipoMensaje == Operador.MSG_NUEVO_CONTRIBUYENTE) {
            op.reg = reg;
        }
        op.enviaMensaje(tipoMensaje, sb.toString());
    }

    public void enviaPresenteAMesa() {
        boolean monitoresComprobados = false;
        try {
            int contador = 0;
            tablaPresentes.principio();
            while (tablaPresentes.haySiguiente()) {
                RegistroCandidatos reg = (RegistroCandidatos) tablaPresentes.siguiente();
                String colaDemandada = reg.getCola();
                String mesaPreferente = reg.getMesa().trim();
                Operador opLibre = null;
                if (mesaPreferente != null && mesaPreferente.length() > 0) {
                    opLibre = operadores.buscaPorMesa(mesaPreferente);
                } else {
                    opLibre = operadores.buscaLibreConCapacidad(colaDemandada);
                }
                if (opLibre != null) {
                    if (!monitoresComprobados) {
                        if (conf.comprobarMonitores && compruebaMonitoresConectados() == false) {
                            return;
                        }
                        monitoresComprobados = true;
                    }
                    enviaMensajeNuevoContribuyente(opLibre, Operador.MSG_NUEVO_CONTRIBUYENTE, reg, null);
                    opLibre.marcarAsignacion(colaDemandada, reg.getCitaMultiple());
                    operadores.sacarDeLibres(opLibre);
                    tablaCandidatos.ponerEstadoDescartado(reg.getId(), opLibre.getNombre(), opLibre.getPuesto(), opLibre.getCentro());
                    gestores.enviarPresenteAMesa(opLibre.getPuesto(), reg.registro());
                    String sala = tablaColas.getSalaEspera(colaDemandada);
                    RegistroAcciones regCambiado = tablaAcciones.cambiaEstado(opLibre.getPuesto(), opLibre.getNombre(), reg.getCitaMultiple(), Operador.ENTRANSITO, colaDemandada);
                    administradores.comunicarCambioEnAcciones(regCambiado.registro());
                    monitores.notificarAlta(reg.getNif(), reg.getNumeroTicket(), opLibre.getPuesto(), sala, reg.getHoraCalculada2(), opLibre.getLocalizacion());
                    if (reg.getCitaMultiple() > 1) {
                        EnviarVinculados(opLibre, reg.getCitaVinculada(), reg.getNumeroTicket());
                    }
                    if (reg.provieneDeCitaPrevia()) {
                        buscarMascitas(opLibre, reg.getNif(), reg.getNumeroTicket());
                    }
                    contador++;
                    ejecucionesPrioridad++;
                    if (ejecucionesPrioridad > conf.prioridadCitaPrevia) {
                        ejecucionesPrioridad = 0;
                        ordenarConPrioridad = false;
                    } else {
                        ordenarConPrioridad = true;
                    }

                    if (operadores.cuentaLibres() == 0 || contador >= conf.colasSimultaneas) {
                        return;
                    }
                }
            }
        } catch (Exception exc) {
            String txt = "Error al enviar Presente a Mesa: ";
            ServidorMain.log.log(Level.WARNING, "{0}\n{1}", new Object[]{txt, exc.getMessage()});
            ServidorMain.logException(txt, exc);
        }
    }

    public void buscarMascitas(Operador op, String nif, String ticket) {
        RegistroCandidatos segundaCola = tablaCandidatos.buscarMasCitasPorNif(nif);
        if (segundaCola != null) {
            op.enviaMensaje(Operador.MSG_MENSAJE_MASCITAS, String.valueOf(segundaCola.getId()), segundaCola.getCola(), ticket);
        }
    }

    public void EnviarVinculados(Operador op, String clave, String ticket) {
        CursorVinculados vinculados = new CursorVinculados(tablaCandidatos);
        vinculados.actualizar(clave);
        while (vinculados.haySiguiente()) {
            RegistroCandidatos reg = (RegistroCandidatos) vinculados.siguiente();
            enviaMensajeNuevoContribuyente(op, Operador.MSG_NUEVO_CONTRIBUYENTEVINCULADO, reg, ticket);
            try {
                tablaCandidatos.ponerEstadoDescartado(reg.getId(), op.getNombre(), op.getPuesto(), op.getCentro());
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    }

    public void enviaPresenteAMesaManual(int idPresente, String mesa, Boolean autoenvio) {
        try {
            Operador operador = operadores.buscaPorMesa(mesa);
            RegistroCandidatos reg = null;
            tablaPresentes.actualizar(new java.sql.Timestamp(System.currentTimeMillis() + 3600000L), false);
            if (operador != null && (reg = tablaPresentes.buscarId(idPresente)) != null) {
                int numeroCitas = reg.getCitaMultiple();
                enviaMensajeNuevoContribuyente(operador, Operador.MSG_NUEVO_CONTRIBUYENTE, reg, null);
                operador.marcarAsignacion(reg.getCola(), numeroCitas);
                operadores.sacarDeLibres(operador);
                tablaCandidatos.ponerEstadoDescartado(idPresente, operador.getNombre(), operador.getPuesto(), operador.getCentro());
                RegistroAcciones regCambiado = tablaAcciones.cambiaEstado(mesa, operador.getNombre(), numeroCitas, Operador.ENTRANSITO, reg.getCola());
                administradores.comunicarCambioEnAcciones(regCambiado.registro());
                if (autoenvio) {
                    return;
                }
                gestores.enviarPresenteAMesa(mesa, reg.registro());
                String sala = tablaColas.getSalaEspera(reg.getCola());
                monitores.notificarAlta(reg.getNif(), reg.getNumeroTicket(), mesa, sala, reg.getHoraCalculada2(), operador.getLocalizacion());
                if (numeroCitas > 1) {
                    EnviarVinculados(operador, reg.getCitaVinculada(), reg.getNumeroTicket());
                }
                if (reg.provieneDeCitaPrevia()) {
                    buscarMascitas(operador, reg.getNif(), reg.getNumeroTicket());
                }
            }
        } catch (Exception exc) {
            ServidorMain.log.log(Level.WARNING, "Error al enviar Presente a Mesa: \n{0}", exc.getMessage());
        }
    }

    public void reenviaPresenteSegundaCola(MensajeOperador mensaje) {
        try {
            tablaCandidatos.PresentesSegundaCita(mensaje.params[2], mensaje.params[3]);
            RegistroCandidatos reg = tablaCandidatos.buscarId(Integer.parseInt(mensaje.params[3]));
            gestores.enviarNuevoCandidato("-1", reg.registro());
        } catch (Exception exc) {
            ServidorMain.log.log(Level.WARNING, "Error en reenvio: \n  {0}\n{1}", new Object[]{mensaje.toString(), exc.getMessage()});
        }
    }

    public void confirmarDescansoOperador(MensajeOperador mensaje) {
        try {
            Operador operador = operadores.get(mensaje.getMesa());
            if (operador != null) {
                operador.setOperativo(false);
            }
            resumen.calcularSuplencias();
        } catch (Exception exc) {
            ServidorMain.log.log(Level.WARNING, "Error en Confirmar Descanso: \n  {0}\n{1}", new Object[]{mensaje.toString(), exc.getMessage()});
        }
    }

    public void reenviaPresenteACola(MensajeOperador mensaje) {
        try {
            RegistroCandidatos reg = tablaCandidatos.buscarId(Integer.parseInt(mensaje.params[3]));
            String codOperador = reg.getOperador();
            String mesa = " ";
            if (mensaje.params.length > 4) {
                mesa = mensaje.params[4];
            }

            if (!codOperador.endsWith("*")) {
                codOperador = codOperador + "*";
                if (codOperador.length() > 15) {
                    codOperador = codOperador.substring(0, 12) + "**";
                }
            }
            RegistroCandidatos nreg;
            if (reg.getHoraPresente().before(reg.getHoraCalculada())) {
                nreg = tablaCandidatos.nuevoCandidatoPresenteReenvio(reg.getNif(), reg.getNombre(), reg.getHoraCita(), reg.getTurno(), reg.getNumeroTicket(),
                        mensaje.params[2], tablaColas.codigoEstadistico(mensaje.params[2]), codOperador, reg.getNiu(), reg.getHoraPresente(), reg.isGestionHuecos(), reg.isDNIe(), mesa, reg.getCentro());
            } else {
                nreg = tablaCandidatos.nuevoCandidatoPresenteReenvio(reg.getNif(), reg.getNombre(), reg.getHoraCita(), reg.getTurno(), reg.getNumeroTicket(),
                        mensaje.params[2], tablaColas.codigoEstadistico(mensaje.params[2]), codOperador, reg.getNiu(), reg.getHoraCalculada(), reg.isGestionHuecos(), reg.isDNIe(), mesa, reg.getCentro());
            }
            gestores.enviarNuevoCandidato("-1", nreg.registro());

        } catch (Exception exc) {
            ServidorMain.log.log(Level.WARNING, "Error en reenvio: \n  {0}\n{1}", new Object[]{mensaje.toString(), exc.getMessage()});
        }
    }

    public void puestoOcupado(MensajeOperador mensaje) {
        Operador operador = operadores.get(mensaje.getMesa());
        if (operador != null) {
            monitores.notificarBaja(mensaje.getMesa());
            operador.setEstado(Operador.OCUPADO);
            accionesCambioDeEstado(mensaje, Operador.OCUPADO);
            gestores.enviarCambioEstadoMesa(operador.getPuesto(), Gestor.MESA_OCUPADA);

        }
        try {
            tablaCandidatos.ponerEstadoAtendido(mensaje.getId());
            gestores.enviarCambioEstadoCandidato(tablaCandidatos.buscarRegistroPorId(mensaje.getId()));
        } catch (Exception exc) {
            ServidorMain.log.log(Level.WARNING, "El registro de candidato con Id {0} provoc\u00f3 un error al pasar a estado ''A''", mensaje.getId());
        }
    }

    public void puestoEnVinculacion(MensajeOperador mensaje) {
        Operador operador = operadores.get(mensaje.getMesa());
        if (operador != null) {
            try {
                operador.setEstado(Operador.OCUPADO);
                tablaCandidatos.ponerEstadoFinalizado(mensaje.getId(), true);
                gestores.enviarCambioEstadoCandidato(tablaCandidatos.buscarRegistroPorId(mensaje.getId()));
                accionesCambioDeEstado(mensaje, Operador.OCUPADO);
                gestores.enviarCambioEstadoMesa(operador.getPuesto(), Gestor.MESA_OCUPADA);
                if (conf.resucitar && operador.reg != null && mensaje.getId() == operador.reg.getId()) {
                    resucitador.add(operador.reg);
                }
            } catch (Exception exc) {
                ServidorMain.log.log(Level.WARNING, "Error en vinculacion: \n  {0}\n{1}", new Object[]{mensaje.toString(), exc.getMessage()});
                ServidorMain.logException("Error en vinculacion: \n  " + mensaje.toString(), exc);
            }
        }
    }

    public void enviarResumenColasAOperador(MensajeOperador mensaje) {
        Operador operador = null;
        try {
            operador = operadores.get(mensaje.getMesa());
            if (operador == null) {
                ServidorMain.log.log(Level.CONFIG, "Operador NULL en enviarResumenColasAOperador. Mensaje= {0}", mensaje.toString());
                return;
            }
            if (!conf.contestarResumenColas) {
                operador.enviaMensaje(Operador.MSG_PENDIENTES, "CerrarBoton");
                return;
            }
            if (resumen != null) {
                String pendientes = resumen.pendientesPorOperador(operador);
                String suplencias = resumen.suplenciasPorOperador(operador);
                StringBuilder mensajePendientes = new StringBuilder("");
                if (pendientes != null) {
                    mensajePendientes.append(pendientes);
                }
                if (suplencias != null) {
                    mensajePendientes.append(suplencias);
                }
                operador.enviaMensaje(Operador.MSG_PENDIENTES, mensajePendientes.toString());
            }
        } catch (Exception exc) {
            System.err.println("ERROR '" + exc.getMessage() + "' en enviarResumenColasAOperador");
            if (operador != null) {
                System.err.println("No se enviaran datos a " + operador.getNombre());
            }
        }
    }

    public void actualizarCatEstadisticasOperador(MensajeOperador mensaje) {
        Operador operador = operadores.get(mensaje.getMesa());
        tablaOperadores.cambiarFavoritasEstadisticas(operador.getNombre(), mensaje.params[2]);
    }

    public void actualizarRegistroCandidato(MensajeOperador mensaje) {
        int id = mensaje.getId();
        String nuevaCategoria = mensaje.params[3];
        tablaCandidatos.cambiarCategoriaEstadistica(id, nuevaCategoria);
    }

    public void solicitadoTiqueColaCero(MensajeOperador mensaje) {
        RegistroCandidatos reg = this.grabarNuevoTiqueColaCero(mensaje);
        if (reg != null) {
            enviaPresenteAMesaManual(reg.getId(), mensaje.getMesa(), true);
            return;
        }
        Operador operador = operadores.get(mensaje.getMesa());
        operador.enviaMensaje(Operador.MSG_ESTADO_BOTONCOLACERO, "XX");
    }

    public void enviarEstadoColasAOperador(MensajeOperador mensaje) {
        Operador operador = operadores.get(mensaje.getMesa());
        if (!conf.enviarEstadoAOperadores) {
            operador.enviaMensaje(Operador.MSG_ESTADO_COLAS, "SIN INFORMACION");
        } else if (!operador.autorizadoParaReenviar) {
            operador.enviaMensaje(Operador.MSG_ESTADO_COLAS, "NO AUTORIZADO");
        } else {
            String listaColas = tablaColas.listaColasConOperador();
            operador.enviaMensaje(Operador.MSG_ESTADO_COLAS, listaColas);
        }
    }

    public void puestoDescansando(MensajeOperador mensaje) {
        Operador operador = operadores.get(mensaje.getMesa());
        if (operador != null) {
            try {
                operadores.sacarDeLibres(operador);
                int estadoAnterior = operador.getEstado();
                operador.setEstado(Operador.DESCANSO);
                if (mensaje.codigo == MensajeOperador.ESTADO_TRABAJO_A_DESCANSO) {
                    tablaCandidatos.ponerEstadoFinalizado(mensaje.getId(), false);
                    gestores.enviarCambioEstadoCandidato(tablaCandidatos.buscarRegistroPorId(mensaje.getId()));
                    if (conf.resucitar) {
                        resucitador.add(operador.reg);

                    }
                } else if (estadoAnterior == Operador.ENTRANSITO && conf.resucitar) {  // mensaje: LIBRE_A_DESCANSO
                    resucitador.add(operador.reg);
                }
                accionesCambioDeEstado(mensaje, Operador.DESCANSO);
                gestores.enviarCambioEstadoMesa(operador.getPuesto(), Gestor.MESA_DESCANSO);
                //   resumen.calcularSuplencias();
            } catch (Exception exc) {
                String txt = "Error al poner estado DESCANSO al operador: " + operador.toString() + "\n  " + mensaje.toString() + "\n" + exc.getMessage();
                ServidorMain.log.warning(txt);
                ServidorMain.logException(txt, exc);
            }
        }
    }

    public void accionesCambioDeEstado(MensajeOperador mensaje, int estado) {
        try {
            RegistroAcciones regCambiado = tablaAcciones.cambiaEstado(mensaje, estado);
            String registroCompleto = regCambiado.registro();
            administradores.comunicarCambioEnAcciones(registroCompleto);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    public void procesarMensajeProductosOperador(MensajeOperador msg) {
        tablaProductos.insertar(msg);
    }

    public void mensajeNotificacionesEncontradas(MensajeOperador msg) {
        try {
            if (logNotificaciones == null) {
                logNotificaciones = new LogGenerico("logAcciones", "notificaciones.log", "Candidatos con notificaciones");
                logNotificaciones.aligerar(64, 32);
            }
            String linea = Fecha.formatoFechaCorta(new Date());
            linea += "|" + msg.getTextoCmd();
            logNotificaciones.escribir(linea);
        } catch (Exception exc) {
            // aqui no queremos hacer nada
        }
    }

    void mensajeDeMesaAAdministrador(MensajeOperador msg) {
        if (msg.params.length == 3) {
            administradores.mensajeDeMesa(msg.getMesa(), msg.getOperador(), msg.params[2]);
        }
    }

    void mensajeDeMesaAGestor(MensajeOperador msg) {
        if (msg.params.length == 3) {
            gestores.enviarMensajeDeMesa(msg.getMesa(), msg.getOperador(), msg.params[2]);
        }
    }

    public void operadorRun(String[] args) {
        operadores.operadorRun(args);
    }

    //</editor-fold>
    //<editor-fold desc="5. Gestores">
    public boolean nuevoGestor(Gestor gestor, String codigo, String tipo) throws Exception {
        gestores.add(gestor);
        gestor.setCodigo(codigo);
        gestor.setTipo(Integer.parseInt(tipo));
        gestor.enviaMensaje(Administrador.MSG_CAMBIO_AUTOMATICO, (Global.modoAutomatico ? "Automatico" : "Manual"));
        gestor.enviarEstadoMesas();
        enviosMasivos.enviarTablaColas(gestor);
        if (tablaHuecos.rangosTieneContenido()) {
            gestor.enviaMensaje(Gestor.MSG_RANGO_HUECOS, tablaHuecos.getcadenaRangos());
        }
        return true;
    }

    public void desconectarGestor(Gestor gestor) throws Exception {
        gestores.remove(gestor);
        System.out.println("Gestor desconectado");
    }

    public void enviarTablaCandidatos(Cliente cliente) {
        enviosMasivos.enviarTablaCandidatos(cliente);
        enviosMasivos.enviarTablaHuecos(cliente);
    }

    public void modificaColaMesaCandidato(MensajeGestor mensaje) {
        try {
            RegCandidato regcan = new RegCandidato(mensaje);
            tablaCandidatos.modificaColaMesaCandidato(regcan);
            RegistroCandidatos rc = tablaCandidatos.buscarId(regcan.Id);
            gestores.enviarCambioEstadoCandidato(rc.registro());
        } catch (Exception exc) {
            exc.printStackTrace();
        }
    }

    public void modificarEstadoCandidato(MensajeGestor mensaje) {
        try {
            RegCandidato regcan = new RegCandidato(mensaje);

            regcan.codigoGestor = gestores.buscarCodigoGestor(regcan.numGestor);
            if (regcan.ticket.length() > 2 || !regcan.estado.equals("P")) {
                tablaCandidatos.cambiaEstado(regcan);
                RegistroCandidatos rc = tablaCandidatos.buscarId(regcan.Id);
                if (rc != null) {
                    gestores.enviarCambioEstadoCandidato(rc.registro());
                    if (conf.resucitar && regcan.estado.equals("P") && rc.resucitable) {
                        resucitador.quitar(rc);
                    }
                }
            } else {
                grabarModificacionTicketCandidato(mensaje);
            }
        } catch (Exception exc) {
            ServidorMain.log.log(Level.WARNING, "ERROR al grabar registro de cambio de estado:\n{0}", mensaje.toString());
        }
    }

    public void grabarNuevoCandidato(MensajeGestor msj) {
        try {
            String colaTicket = msj.getCola();
            RegistroColas regNumera = tablaColas.buscarCola(colaTicket);
            if (regNumera == null) {
                throw new Exception("Letra Cola no identificada: " + colaTicket);
            }
            String niu = " ";
            String colaReal = msj.getCola();
            RegistroColas regCola = tablaColas.buscarCola(colaReal);
            java.sql.Timestamp hora = msj.getHoraHHMM();
            java.sql.Timestamp horaBonificada = hora;
            Gestor remitente = gestores.buscarGestor(Integer.parseInt(msj.getNumeroGestor()));
            // Comprobación de huecos en las colas de cita previa
            boolean usoGestionHuecos = false;
            if (regCola.isCitaPrevia()) {
                niu = msj.getNif();
                if (conf.gestionHuecosActivada && !msj.ignorarHuecos()) {
                    RegistroHuecos regHueco = tablaHuecos.horaCitaDisponible(hora, colaReal);
                    if (regHueco == null) {              // No hay hueco...               
                        // Enviar mensaje de intento fallido
                        remitente.enviaMensaje(Gestor.MSG_HUECO_OCUPADO, hora.toString(), colaReal);
                        return;
                    } else {
                        // Enviar mensaje de cancelacion de hueco. Parámetros: cola, hora, cuantos
                        gestores.enviarMensaje(Gestor.MSG_OCUPAR_HUECO, regHueco.getCola(), regHueco.getHoraCita().toString(), "1");
                        usoGestionHuecos = true;
                    }
                }
            }
            if (regCola.getBonificacion() != 0) {
                horaBonificada = new java.sql.Timestamp(hora.getTime() - (regCola.getBonificacion() * 60000));
            }
            int numero = regNumera.getNumero() + 1;
            if (numero >= 900) {
                numero = 1;
            }
            regNumera.setNumero(numero); // incrementamos el numero en la cola

            String ticket = colaTicket + "-" + numero;
            tablaColas.update(regNumera);

            RegistroCandidatos regCandidato = tablaCandidatos.nuevoCandidatoPresente(msj.getNif(),
                    msj.getNombre(), hora, Global.main.getTurno(), ticket,
                    colaReal, tablaColas.codigoEstadistico(colaReal), remitente.getCodigo(),
                    niu, horaBonificada, usoGestionHuecos, msj.usoDNIe(), " ");
            if (regCandidato != null) {
                gestores.enviarNuevoCandidato(msj.getNumeroGestor(), regCandidato.registro());
            } else {
                throw new Exception("tablaCandidatos.nuevoCandidato devolvió null");
            }
        } catch (Exception exc) {
            ServidorMain.log.log(Level.WARNING, "ERROR al grabar nuevo candidato\n{0}\n{1}", new Object[]{msj.toString(), exc.getMessage()});
        }
    }

    public void horarecibidaGestor(MensajeGestor msj) {
        System.out.println("Gestor " + msj.toString4());
    }

    public void grabarNuevoCandidatoNoPresente(MensajeGestor msj) {
        try {
            String cola = msj.getCola();
            java.sql.Timestamp hora = msj.getHora();
            Gestor remitente = gestores.buscarGestor(Integer.parseInt(msj.getNumeroGestor()));
            RegistroHuecos regHueco = tablaHuecos.horaCitaDisponible(hora, cola);
            if (regHueco == null) {              // No hay hueco...
                // Enviar mensaje de intento fallido
                remitente.enviaMensaje(Gestor.MSG_HUECO_OCUPADO, hora.toString(), cola);
                return;
            }
            // Enviar mensaje de cancelacion de hueco. Parámetros: cola, hora, cuantos
            gestores.enviarMensaje(Gestor.MSG_OCUPAR_HUECO, regHueco.getCola(), hora.toString(), "1");
            String turnoAsignado = regHueco.getTurno();
            RegistroCandidatos regCandidato = tablaCandidatos.nuevoCandidatoReserva(msj.getNif(), msj.getNombre(),
                    hora, turnoAsignado, cola, tablaColas.codigoEstadistico(cola), msj.usoDNIe());
            if (regCandidato != null) {
                String registroCompleto = regCandidato.registro();
                gestores.enviarNuevoCandidato("0", registroCompleto);
                remitente.enviaMensaje(Gestor.MSG_NUEVO_CANDIDATO_NOPRESENTE, registroCompleto);
            } else {
                throw new Exception("tablaCandidatos.nuevoCandidatoNoPresente devolvió null");
            }
        } catch (Exception exc) {
            ServidorMain.log.log(Level.WARNING, "ERROR al grabar nuevo candidato\n{0}\n{1}", new Object[]{msj.toString(), exc.getMessage()});
        }
    }

    public RegistroCandidatos grabarNuevoTiqueColaCero(MensajeOperador msj) {
        try {
            if (colaCero0.equals("")) {
                return null;
            }
            RegistroColas regCola = tablaColas.buscarColaCeroActiva(colaCero0);
            if (regCola == null) {
                throw new Exception("No se ha definido Cola con codigo estadístico CERO");
            }
            Operador operador = operadores.get(msj.getMesa());
            if (operador.getEstado() != Operador.LIBRE) {
                return null;
            }
            if (Global.modoAutomatico) {
                if (conf.forzarAutoenvio == false) {
                    if (operador.tieneAlgunaCapacidadActiva(colaCero0)) {
                        return null;
                    }
                }
            }
            String nif = msj.params[2];
            String nombre = msj.params[3];

            java.sql.Timestamp hora = Fecha.ahora();
            if (regCola.isCitaPrevia()) {
                //niu = msj.getNif();
            }
            int numero = regCola.getNumero() + 1;
            if (numero > 999) {
                numero = 1;
            }
            regCola.setNumero(numero); // incrementamos el numero en la cola
            String cola = regCola.getCola();
            String ticket = cola + "-" + numero;
            tablaColas.update(regCola);

            RegistroCandidatos regCandidato = tablaCandidatos.nuevoCandidatoPresente(nif,
                    nombre, hora, Global.main.getTurno(), ticket,
                    cola, "0", operador.getNombre(), nif, hora, false, false, operador.centro);
            if (regCandidato != null) {
                gestores.enviarNuevoCandidato("_", regCandidato.registro());
                return regCandidato;
            } else {
                throw new Exception("tablaCandidatos.nuevoCandidato devolvió null");
            }
        } catch (Exception exc) {
            ServidorMain.log.log(Level.WARNING, "ERROR al grabar AutoEnvio colaCERO\n{0}", exc.getMessage());
        }
        return null;
    }

    public void grabarModificacionTicketCandidato(MensajeGestor msj) {
        try {
            String cola = msj.params[13];
            RegistroColas reg = tablaColas.buscarCola(cola);
            if (reg == null) {
                throw new Exception("Letra Cola no identificada: " + cola);
            }
            int numero = reg.getNumero() + 1;
            if (numero >= 900) {
                numero = 1;
            }
            reg.setNumero(numero);
            java.sql.Timestamp hora = java.sql.Timestamp.valueOf(msj.params[4]);
            RegCandidato regcan = new RegCandidato(msj);
            regcan.codigoGestor = gestores.buscarCodigoGestor(regcan.numGestor);
            regcan.ticket = cola + "-" + numero;
            tablaColas.update(reg);
            if (reg.getBonificacion() != 0) {
                hora = new java.sql.Timestamp(hora.getTime() - (reg.getBonificacion() * 60000));
                regcan.horaCalculada = hora;
            }
            RegistroCandidatos regc = tablaCandidatos.cambiaEstado(regcan);
            if (regc != null) {
                gestores.enviarNuevoCandidato(msj.getNumeroGestor(), regc.registro());
            } else {
                ServidorMain.log.log(Level.WARNING, "No se encuentra en la tabla de candidatos el registro con Id. {0}", regcan.Id);
            }

        } catch (Exception exc) {
            ServidorMain.log.log(Level.WARNING, "ERROR al grabar modificacion tique candidato\n{0}\n{1}", new Object[]{msj.toString(), exc.getMessage()});
        }
    }

    public void enviarMonitores(Gestor gestor) {
        String nombres;
        if (conf.comprobarMonitores) {
            Global.numMonitoresNecesarios = monitores.marcarNecesarios();
            marcaMonitoresVivos();
            nombres = monitores.listaNombres();
        } else {
            nombres = "MONITORES NO";
        }
        gestor.enviaMensaje(Gestor.MSG_MONITORES_CONECTADOS, nombres);
    }

    public void enviarPuestos(Gestor gestor) {
        gestor.enviarSalas(Global.tablaPuestos.listaSalasAtencion());
        gestor.enviarPuestos(Global.tablaPuestos.listaPuestos());
    }

    public void cambiaModoAutomatico(String modo) {
        if (modo.equals("true")) {
            if (conf.comprobarMonitores) {
                Global.numMonitoresNecesarios = monitores.marcarNecesarios();
                if (Global.numMonitoresNecesarios == 0) {
                    Global.modoAutomatico = false;
                    gestores.enviarForzarManual();
                    administradores.enviarMensaje(Administrador.MSG_CAMBIO_AUTOMATICO, "Manual|Monitores");
                    return;
                }
            }
            Global.modoAutomatico = true;
            administradores.enviarMensaje(Administrador.MSG_CAMBIO_AUTOMATICO, "Automatico");
            gestores.enviarMensaje(Administrador.MSG_CAMBIO_AUTOMATICO, "Automatico");
            System.out.println(" *** Modo AUTOMÁTICO ***");
        } else {
            Global.modoAutomatico = false;
            administradores.enviarMensaje(Administrador.MSG_CAMBIO_AUTOMATICO, "Manual");
            gestores.enviarMensaje(Administrador.MSG_CAMBIO_AUTOMATICO, "Manual");
            System.out.println(" *** Modo MANUAL ***");
        }
    }

    public void mensajeDeGestorAAdministrador(MensajeGestor msj) {
        if (msj.params.length == 3) {
            administradores.mensajeDeMesa(msj.params[0], msj.params[1], msj.params[2]);
        }
    }

    public void mensajeDeGestorAOperadores(MensajeGestor msj) {
        if (msj.params.length == 3) {
            operadores.enviaMensaje(Operador.MSG_MENSAJE_DEL_GESTOR, "NoRetorno", msj.params[2]);
        }
    }

    public void mensajeDeGestorAOperador(MensajeGestor msj) {
        if (msj.params.length == 3) {
            String origen = msj.params[0];  // gestor de vuelta para confirmacion
            String destino = msj.params[1];   // mesa destino del mensaje
            String texto = msj.params[2];
            Operador op = operadores.get(destino);
            if (op != null) {
                op.enviaMensaje(Operador.MSG_MENSAJE_DEL_GESTOR, origen, texto);
            }
        }
    }

    //</editor-fold>
    //<editor-fold desc="6. Administradores ">
    public boolean nuevoAdministrador(Administrador adm) throws Exception {
        resumen.recargarDatos();
        if (!adm.enviarTablas()) {
            return false;
        }
        administradores.add(adm);
        String estado = "Manual";
        if (Global.modoAutomatico) {
            estado = "Automatico";
        }
        adm.enviaMensaje(Administrador.MSG_ENVIO_RESUMEN_RETRASOS, resumen.toString());
        administradores.enviarMensaje(Administrador.MSG_CAMBIO_AUTOMATICO, estado);
        if (tablaHuecos.rangosTieneContenido()) {
            adm.enviaMensaje(Administrador.MSG_ENVIO_RANGO_HUECOS, tablaHuecos.getcadenaRangos());
        }
        return true;
    }

    public void desconectarAdministrador(Administrador adm) throws Exception {
        administradores.remove(adm);
        System.out.println("Administrador desconectado");
    }

    public void cambiaCapacidades(MensajeAdministrador msj) {
        String codigoOperador = msj.params[0];
        String caps;
        String supls;
        if (msj.params.length < 2) {
            caps = "";
        } else {
            caps = msj.params[1];
        }
        if (msj.params.length < 3) {
            supls = "";
        } else {
            supls = msj.params[2];
        }
        tablaOperadores.cambiarCapacidad(codigoOperador, caps, supls);
        if (!operadores.cambiarCapacidad(codigoOperador, caps, supls)) {
            operadoresDesconectados.add(codigoOperador, caps);
        }
        administradores.enviarMensaje(Administrador.MSG_CAMBIO_CAPACIDAD, codigoOperador, caps, supls);
    }

    public void refrescarAdministradoresYMonitores() {
        resumen.recargarDatos();
        String mensaje = resumen.toString();
        if (!mensaje.equals(ultimoResumen)) {
            try {
                administradores.refrescar(mensaje);
                monitores.refrescar(mensaje);
            } catch (ConcurrentModificationException exc) {
            }
            ultimoResumen = mensaje;
        }
    }

    public void mensajeDeAdministradorAOperador(MensajeAdministrador mensaje) {
        if (mensaje.params.length == 3) {
            String origen = mensaje.params[0];  // administrador de vuelta para confirmacion
            String destino = mensaje.params[1];   // mesa (o mesas) destino del mensaje
            String[] destinos = Global.split(destino, ',');
            if (destinos.length > 1) {
                origen = "NoRetorno";  // si es para mas de una mesa no hay retorno
            }
            String texto = mensaje.params[2];
            for (String d : destinos) {
                Operador op = operadores.get(d);
                op.enviaMensaje(Operador.MSG_MENSAJE_DEL_ADMINISTRADOR, origen, texto);
            }
        }
    }

    public void mensajeDeAdministradorAGestor(MensajeAdministrador mensaje) {
        if (mensaje.params.length == 3) {
            String origen = mensaje.params[0];  // administrador de vuelta para confirmacion
            String texto = mensaje.params[2];
            gestores.enviarMensajeDeAdministrador(origen, texto);
        }
    }

    public void mensajeDeAdministradorAOperadores(MensajeAdministrador mensaje) {
        if (mensaje.params.length == 3) {
            String texto = mensaje.params[2];
            operadores.enviaMensaje(Operador.MSG_MENSAJE_DEL_ADMINISTRADOR, "NoRetorno", texto);
        }
    }

    public void logAdministradorConectado(MensajeAdministrador msg) {
        try {
            if (logConexiones == null) {
                logConexiones = new LogGenerico("logAcciones", "conexiones.log", "Conexiones de Administradores");
                logConexiones.aligerar(12, 6);
            }
            String linea = Fecha.formatoFechaCorta(new Date()) + " " + Fecha.formatoHoraHMS(new Date())
                    + "  " + msg.handler.getSocket().getInetAddress().toString();
            logConexiones.escribir(linea);
        } catch (Exception exc) {
            // aqui no queremos hacer nada
        }
    }

    public void enviarFicheroDatosTicket(String tabla, String id, Cliente cli) {
        String resu = tablaCandidatos.datosNotaFichero(tabla, id);
        cli.enviaMensaje(Administrador.MSG_DATOS_FICHERO_TICKET, resu);
    }

    public void enviarDatosTicket(String ticket, Cliente cli) {
        RegistroCandidatos[] regs = null;
        try {
            if (ticket.length() > 8) {
                regs = tablaCandidatos.datosPorNif2(ticket);
            } else {
                regs = tablaCandidatos.datosPorTicket2(ticket);
            }
            String mensaje = "";
            if (regs.length == 0) {
                mensaje = "TIQUE NO ENCONTRADO";
            } else {
                mensaje = regs[0].registro();
                for (int i = 1; i < regs.length; i++) {
                    mensaje += "##" + regs[i].registro();
                }
            }
            cli.enviaMensaje(Administrador.MSG_DATOS_TICKET, mensaje);
        } catch (Exception eex) {

        }
    }

    public void enviarDatosHistoricosTicket(String nif, Cliente cli) {
        RegistroCandidatos[] regs = null;
        try {
            regs = tablaCandidatos.datosPorNif2Historico(nif);
            String mensaje = "";
            if (regs.length == 0) {
                mensaje = "TIQUE NO ENCONTRADO";
            } else {
                mensaje = regs[0].registro();
                for (int i = 1; i < regs.length; i++) {
                    mensaje += "##" + regs[i].registro();
                }
            }
            cli.enviaMensaje(Administrador.MSG_DATOS_TICKET, mensaje);
        } catch (Exception eex) {

        }
    }

    public void cerrarCola(String codCola, String cerrada, String texto) {
        RegistroColas reg = tablaColas.cambiarEstado(codCola, (cerrada.equals("si")));
        if (reg != null) {
            reg.setTextoCerrada(texto);
            String registro = reg.registro();
            gestores.enviarCambioEstadoCola(codCola, cerrada, texto);
            administradores.enviarMensaje(Gestor.MSG_REGISTRO_COLAS, registro);
            operadores.enviaMensaje(Gestor.MSG_REGISTRO_COLAS, registro);
            resumen.recargarDatos();
            administradores.refrescar(resumen.toString());
        }
    }

    public void modificarUmbralSustitutos(String codCola, String nuevoValor) {
        RegistroColas reg = tablaColas.cambiarUmbralSustituciones(codCola, nuevoValor);
        if (reg != null) {
            administradores.enviarMensaje(Gestor.MSG_REGISTRO_COLAS, reg.registro());
        }
        resumen.calcularSuplencias();
        resumen.recargarDatos();
        administradores.refrescar(resumen.toString());
    }

    public void altaOperador(String codigo, String nombre, String centro) {
        if (centro.length() < 5) {
            centro = "";
        }
        RegistroOperadores reg = tablaOperadores.altaOperador(codigo, nombre, centro);
        if (reg != null) {
            administradores.enviarMensaje(Administrador.MSG_ENVIO_REGISTRO_OPERADORES, reg.registro());
        }
    }

    public void repetirEnvio(String desde, String hasta) {
    }

    public void repetirRecepcion() {
    }

    public void enviarHuecosAdministrador(String[] params, Cliente cli) {
        String mapa = tablaHuecos.mapaHuecos(params[0], params[1]);
        if (mapa == null) {
            mapa = "sin huecos";
        }
        cli.enviaMensaje(Administrador.MSG_HUECOS_POR_COLA, params[0], params[1], mapa);
    }

    public void enviarListaAdministradores(Cliente cli, String numeroAdministrador) {
        String lista = administradores.listaNombres(numeroAdministrador);
        cli.enviaMensaje(Administrador.MSG_ENVIO_LISTA_ADMINISTRADORES, lista);
    }

    public void enviarMensajeAAdministradorPorNombre(String p0, String p1, String p2, String p3) {
        administradores.mensajeParaAdministrador(p0, p1, p2, p3);
    }

    public void enviarFicheroNotificaciones(Cliente cli) {
        if (logNotificaciones == null) {
            logNotificaciones = new LogGenerico("logAcciones", "notificaciones.log", "Candidatos con notificaciones");
        }
        cli.enviaMensaje(Administrador.MSG_ENVIO_FICHERO_NOTIFICACIONES, logNotificaciones.leerAString());
    }

    public void enviarFicheroPresentes(Cliente cli) {
        int contador = 1;
        tablaPresentes.actualizar(new java.sql.Timestamp(System.currentTimeMillis() + 3600000L), false);
        tablaPresentes.principio();
        StringBuilder p = new StringBuilder();
        while (tablaPresentes.haySiguiente()) {
            RegistroCandidatos reg = (RegistroCandidatos) tablaPresentes.siguiente();
            p.append(" ").append(contador).append("|").append(reg.getCola()).append("|").append(reg.getNumeroTicket()).append("|").append(reg.getNif()).append("|").append(reg.getNombre()).append("|").append(reg.getHoraCalculada()).append("|").append(reg.getHoraPresente()).append("!#");
            contador++;
            if (contador > 100) {
                break;
            }
        }
        cli.enviaMensaje(Administrador.MSG_ENVIO_FICHERO_PRESENTES, p.toString());
    }

    public void enviarModificacionRango(String[] params) {
        if (params.length == 0) {
            tablaHuecos.borrarRango();
        } else {
            tablaHuecos.modificarRangoHuecos(params);
        }
        administradores.enviarMensaje(Administrador.MSG_ENVIO_RANGO_HUECOS, tablaHuecos.getcadenaRangos());
        gestores.enviarMensaje(Gestor.MSG_RANGO_HUECOS, tablaHuecos.getcadenaRangos());
    }

    public void generarUnHueco(MensajeOperador mensaje) {
        String cola = mensaje.params[2];
        String turno = mensaje.params[3];
        java.sql.Timestamp t1 = java.sql.Timestamp.valueOf(mensaje.params[4]);
        ArrayList<RegistroHuecos> al = tablaHuecos.generarUnHueco(cola, turno, t1);
        if (al.size() > 0) {
            String mapa = tablaHuecos.mapaHuecos(al);
            gestores.enviarHuecos(cola, turno, mapa);
            tablaHuecos.incorporarHuecos(al);
        }
    }

    public void generarHuecos(String[] params) {
        try {
            String cola = params[0];
            String turno = params[1];
            java.sql.Timestamp t1 = java.sql.Timestamp.valueOf(params[2]);
            java.sql.Timestamp t2 = java.sql.Timestamp.valueOf(params[3]);
            int duracion = Integer.parseInt(params[4]);
            int numero = Integer.parseInt(params[5]);
            cola = tablaHuecos.buscarRangoHuecosPorCola(cola);
            if (numero > 0) {
                ArrayList<RegistroHuecos> al = tablaHuecos.generarHuecos(cola, turno, t1, t2, duracion, numero);
                if (al.size() > 0) {
                    String mapa = tablaHuecos.mapaHuecos(al);
                    gestores.enviarHuecos(cola, turno, mapa);
                    tablaHuecos.incorporarHuecos(al);
                }
            } else {
                ArrayList<String[]> al = tablaHuecos.generarAntiHuecos(cola, turno, t1, t2, duracion, numero);
                for (String[] al1 : al) {
                    ocuparHuecos(al1);
                }
            }
        } catch (Exception exc) {
            exc.printStackTrace();
        }
    }

    LogGenerico logOcuparHuecos = new LogGenerico("HuecosOcupados.log", "ocupacion de huecos por Administrador");

    public void ocuparHuecos(String[] params) {
        try {
            String textoOcupar = tablaHuecos.ocuparHuecos(params[0], params[1], params[2]);
            gestores.enviarMensaje(Gestor.MSG_OCUPAR_HUECO, params[0], params[1], params[2]);
            if (conf.logOcuparHuecos) {
                logOcuparHuecos.escribir(Fecha.formatoHoraHMS(new Date()) + " " + params[0] + " " + params[1] + " " + params[2]);
                if (!textoOcupar.equalsIgnoreCase("OK")) {
                    logOcuparHuecos.escribir(textoOcupar);
                }
            }
        } catch (Exception exc) {
            System.out.println("Error al eliminar huecos");
            System.out.println(exc.toString());
        }
    }

    public void pedirHuecoOperador(MensajeOperador mensaje) {
        try {
            if (!conf.gestionHuecosActivada) {
                return;
            }
            RegistroHuecos regHueco = tablaHuecos.dameCitaDisponible(mensaje.params[2], Global.main.getTurno());
            Operador op = operadores.get(mensaje.params[0]);
            if (regHueco != null) {
                gestores.enviarMensaje(Gestor.MSG_OCUPAR_HUECO, regHueco.getCola(), regHueco.getHoraCita().toString(), "1");
                op.enviaMensaje(Operador.MSG_HUECO_CONCEDIDO, regHueco.getCola(), Global.main.getTurno(), regHueco.getHoraCita().toString());
                if (conf.logOcuparHuecos) {
                    logOcuparHuecos.escribir(Fecha.formatoHoraHMS(new Date()) + " " + regHueco.getCola() + " " + regHueco.getHoraCita().toString() + " " + mensaje.params[0] + " " + mensaje.params[1]);
                }
            } else {
                op.enviaMensaje(Operador.MSG_HUECO_DENEGADO, mensaje.params[2]);
            }
        } catch (Exception exc) {
            exc.printStackTrace();
        }
    }

    public void pedirlistaOperadores(MensajeOperador mensaje) {
        try {
            Operador op = operadores.get(mensaje.params[0]);
            String listaope = operadores.listaOperadorConCapacidad(mensaje.params[2]);
            op.enviaMensaje(Operador.MSG_LISTAOPERADORES, listaope);
        } catch (Exception exc) {
            exc.printStackTrace();
        }
    }

    public void grabarNota(MensajeOperador mensaje) {
        try {
            int id = mensaje.getId();
            int len = mensaje.params.length;
            String texto2 = mensaje.argumentos;
            int pos = texto2.indexOf("{\\rtf");
            String texto;
            if (pos > 0) {
                texto = texto2.substring(pos);
            } else {
                texto = mensaje.params[len - 1];
            }
            tablaCandidatos.grabarNota(id, texto);
        } catch (Exception exc) {
            exc.printStackTrace();
        }
    }

    //</editor-fold>
    public void notificarModoRecuperacion() {
        String msj = "El sistema está temporalmente desconectado del gestor de Bases de Datos#"
                + "Contacte con el administrador de su red";
        administradores.mensajeDeMesa("servidor", "0", msj);
    }

    //<editor-fold desc="7. Monitores ">
    public boolean nuevoMonitor(Monitor monitor) throws Exception {
        resumen.recargarDatos();
        String strResumen = resumen.toString();
        monitores.add(monitor);
        enviosMasivos.enviarTablaColas(monitor);
        ArrayList<String> enTransito = tablaCandidatos.candidatosEnTransito(tablaColas, operadores);
        for (String enTransito1 : enTransito) {
            monitor.enviaMensaje(Monitor.MSG_EN_TRANSITO, enTransito1);
        }
        monitor.enviaMensaje(Monitor.MSG_ENVIO_RESUMEN_RETRASOS, strResumen);
        monitores.enviarTicketsPendientes(monitor);
        return true;
    }

    public void desconectarMonitor(Monitor mo) throws Exception {
        if (!mo.muerto) {
            if (conf.comprobarMonitores) {
                compruebaSiMonitorEsNecesario(mo);
            }
            System.out.println("Monitor desconectado: " + mo.getIp());
            monitores.remove(mo);
        }
    }

    void compruebaSiMonitorEsNecesario(Monitor mo) {
        if (mo.necesario && Global.modoAutomatico) {
            gestores.enviarMonitorDesconectado(mo.nombre);
            System.out.println(" *** Desconectado monitor necesario: ");
            System.out.println("  " + mo.nombre + " en " + mo.ipAdd);
            administradores.enviarMensaje(Administrador.MSG_CAMBIO_AUTOMATICO, "Manual|" + mo.nombre);
            cambiaModoAutomatico("false");
        }
    }

    void monitorVivo(String ip) {
        monitores.marcarMonitorVivo(ip);
        monitoresVivos++;
    }

    void marcaMonitoresVivos() {
        monitoresVivos = 0;
        monitores.marcarTodosMuertos();
        monitores.enviarAvisoDeVida();
        long ahora = System.currentTimeMillis();
        while (monitoresVivos < Global.numMonitoresNecesarios) {
            long lapso = System.currentTimeMillis();
            if ((lapso - ahora) > 10000) {
                return;
            }
            Global.sleep(100);
        }
    }
    int problemasMonitores = 0;

    boolean compruebaMonitoresConectados() {
        if (Global.numMonitoresNecesarios == 0) {
            Global.numMonitoresNecesarios = monitores.marcarNecesarios();
        }
        monitoresVivos = 0;
        if (problemasMonitores == 0) {
            monitores.marcarTodosMuertos();
        }
        monitores.enviarAvisoDeVida();
        long ahora = System.currentTimeMillis();
        while (monitoresVivos < Global.numMonitoresNecesarios) {
            long lapso = System.currentTimeMillis();
            if ((lapso - ahora) > 5000) {
                problemasMonitores++;
                if (problemasMonitores > 2) {
                    System.out.println(" *** Sin conexión con monitores necesarios: ");
                    String muertos = monitores.listarMonitoresMuertos();
                    cambiaModoAutomatico("false");
                    administradores.enviarMensaje(Administrador.MSG_CAMBIO_AUTOMATICO, "Manual|" + muertos);
                    gestores.enviarForzarManual();
                }
                return false;
            }
            Global.sleep(100);
        }
        problemasMonitores = 0;
        return true;
    }

    public void apagarMonitores() {
        monitores.apagarMonitores("ENCENDIDO_INMEDIATO");
    }

    public void reactivarTicketsPorMonitor(MensajeMonitor msj) {
        for (String ticket : msj.params) {
            RegistroCandidatos[] rcs = tablaCandidatos.datosPorTicket(ticket);
            RegistroCandidatos rc = rcs[rcs.length - 1];   // si hay mas de uno nos quedamos con el ultimo...
            tablaCandidatos.resucitar(rc, gestores);
        }
    }

    //</editor-fold>
    //<editor-fold desc="8. Gestion colas y mensajes internos">
    public void paradaCautelar(String mensaje) {
        System.out.println(mensaje);
        administradores.enviarMensaje(Administrador.MSG_MENSAJE_DEL_SERVIDOR, mensaje);
        apagarClientes();
        try {
            Global.main.servidor.stopServer();
        } catch (Exception ex) {
        }
    }

    public void apagarClientes() {
        operadores.apagarOperadores("silencioso");
        gestores.apagarGestores("x");
        monitores.apagarMonitores("x");
        administradores.apagarAdministradores("x");
    }

    public void mensajeRecibido(String origen) {
        if (origen.startsWith("autoServicio")) {
        } else if (origen.startsWith("gestor")) {
            try {
                int numGestor = Integer.parseInt(origen.substring(6));
                Gestor g = gestores.buscarGestor(numGestor);
                if (g != null) {
                    g.enviaMensaje(Gestor.MSG_MENSAJE_RECIBIDO, "");
                }
            } catch (Exception exc) {
                servidor.ServidorMain.log.log(Level.WARNING, "ERROR en mensajeRecibido de ''gestor'':{0}", exc.getMessage());
            }
        } else if (origen.startsWith("administrador")) {
            try {
                int numAdmin = Integer.parseInt(origen.substring(13));
                Administrador a = administradores.buscarAdministrador(numAdmin);
                if (a != null) {
                    a.enviaMensaje(Administrador.MSG_MENSAJE_RECIBIDO, "");
                }

            } catch (Exception exc) {
                servidor.ServidorMain.log.log(Level.WARNING, "ERROR en mensajeRecibido de ''administrador'':{0}", exc.getMessage());
            }
        } else {
            Operador op = operadores.get(origen);
            if (op != null) {
                op.enviaMensaje(Operador.MSG_MENSAJE_RECIBIDO, "");
            }
        }
    }

    public void autoMensaje(String txt) {
        MensajeInterno mensaje = new MensajeInterno(txt, null);
        encola(mensaje);
    }

    public void autoMensaje(int codigo) {
        MensajeInterno mensaje = new MensajeInterno(codigo, null);
        encola(mensaje);
    }

    public void cambiodeTurnoProgramado() {
        if (cambioDeTurno("T")) {
            if (!BaseDatos.bd().recuperacionActivada) {
                Global.main.reiniciarContadoresColas();
                GestorColas.getGestor().tablaColas.reabrirColasCerradas();
            }
        }
    }

    public boolean cambioDeTurno(String nuevoTurno) {
        if (Global.main.getTurno().equals(nuevoTurno)) {
            return false;
        }
        Global.main.setTurno(nuevoTurno);
        if (!BaseDatos.bd().recuperacionActivada) {
            LimpiarTablaAccionesPorTurno();
        }
        gestores.comunicaCambioTurno(nuevoTurno);
        return true;
    }

    public void listar(String cuales) {
        boolean sinParametro = (cuales.length() == 0);
        if (sinParametro) {
            operadores.listar();
            gestores.listar();
            administradores.listar();
            monitores.listar();
            return;
        }
        if (cuales.startsWith("OPER")) {
            operadores.listar();
        } else if (cuales.startsWith("GEST")) {
            gestores.listar();
        } else if (cuales.startsWith("ADMI")) {
            administradores.listar();
        } else if (cuales.startsWith("MONI")) {
            monitores.listar();
        } else if (cuales.startsWith("RESU")) {
            if (resucitador != null) {
                resucitador.listar();
            }
        } else if (cuales.startsWith("COLA")) {
            tablaColas.listar();
        } else {
            System.out.println("ERROR EN COMANDO.");
        }
    }

    public void ping(Pinger pinger, String cuales) {
        boolean vale = false;
        boolean sinParametro = (cuales.length() == 0);
        if (sinParametro || cuales.startsWith("OPER")) {
            vale = pinger.nuevoDestino(operadores.iterator(), "Operador ");
        }
        if (sinParametro || cuales.startsWith("GEST")) {
            vale = pinger.nuevoDestino(gestores.iterator(), "Gestor ");
        }
        if (sinParametro || cuales.startsWith("ADMI")) {
            vale = pinger.nuevoDestino(administradores.iterator(), "Administrador ");
        }
        if (sinParametro || cuales.startsWith("MONI")) {
            vale = pinger.nuevoDestino(monitores.iterator(), "Monitor ");
        }
        if (vale) {
            pinger.comenzar();
        }
    }

    public void encola(Mensaje msg) {
        synchronized (colaMensajes) {
            colaMensajes.encola(msg);
            colaMensajes.notifyAll();
        }
    }

    public void esperarAColaVacia() {
        colaMensajes.volcarAFichero("restoMensajes.log");
        int n = 1;
        while (n > 0) {
            synchronized (colaMensajes) {
                n = colaMensajes.size();
            }
            Global.sleep(1000);
        }
    }

    class ThreadLecturaColaMensajes extends Thread {

        @Override
        public void run() {
            while (true) {
                try {
                    Mensaje mensaje = null;
                    do {
                        synchronized (colaMensajes) {
                            mensaje = colaMensajes.desencola();
                        }
                        if (mensaje != null) {
                            java.util.Date hora1 = new java.util.Date();
                            java.util.Date hora2 = hora1;
                            try {
                                mensaje.procesar();
                                if (!Configuracion.DEBUG) {
                                    hora2 = new java.util.Date();
                                    long retraso = hora2.getTime() - hora1.getTime();
                                    if (retraso > maxRetrasoMensaje) {
                                        ServidorMain.log.log(Level.WARNING, "RETRASO de {0} mseg. procesando: \n  {1}", new Object[]{Long.toString(retraso), mensaje.toString3()});
                                    }
                                }
                            } catch (NullPointerException nexc) {
                                ServidorMain.log.log(Level.WARNING, "ERROR {0}\nen el proceso del mensaje {1}", new Object[]{nexc.getMessage(), mensaje.toString()});
                                nexc.printStackTrace();
                            } catch (Exception e) {
                                ServidorMain.log.log(Level.WARNING, "ERROR {0}\nen el proceso del mensaje {1}", new Object[]{e.getMessage(), mensaje.toString()});
                            }
                            if (Global.main.guardarMensajes && mensaje.codigo != 507 && mensaje.codigo != 299 && mensaje.codigo != 0) {
                                try {
                                    Global.main.logMensajes.escribirRapido(Fecha.formatoHoraHMSm(hora2) + "  " + mensaje.toString3());
                                } catch (Exception exc) {
                                    //System.err.println("(Guardando mensaje en log)" + exc.getMessage());
                                }
                            }
                        }
                        synchronized (colaMensajes) {
                            if (colaMensajes.size() < 1) {
                                colaMensajes.wait(200);    // despertar cada dos milisegundos
                            }
                        }
                    } while (mensaje != null);
                } catch (Exception e) {
                    ServidorMain.log.log(Level.SEVERE, "ERROR en la extraccion de mensajes de la cola: {0}", e.getMessage());
                }
            }
        }
    }

    int contarPresentes() {
        java.sql.Timestamp ahora = new java.sql.Timestamp(System.currentTimeMillis());
        if (ejecucionesPrioridad < conf.prioridadCitaPrevia) {
            ordenarConPrioridad = true;
            tablaPresentes.actualizar(ahora, true);
        } else {
            tablaPresentes.actualizar(ahora, false);
            ordenarConPrioridad = false;
        }
        return tablaPresentes.count();
    }

    public void informarEstadoCola() {
        System.err.println("Mensajes en cola: " + colaMensajes.size());
    }

    public void compruebaColaPresentes() {
        try {
            int numPresentes = contarPresentes();
            if (numPresentes > 0 && operadores.cuentaLibres() > 0) {
                enviaPresenteAMesa();
            }
        } catch (ConcurrentModificationException exc) {
            ServidorMain.log.warning("Acceso concurrente a la tabla PRESENTES.");
        }
    }

    public void LimpiarTablaAccionesPorTurno() {
        try {
            String campos = "Puesto, Codigo, Turno, TiempoLibre, TiempoOcupado, TiempoDescanso, "
                    + "HoraUltimoMovimiento, FechaConexion, Centro, Citas, CitaMultiple, Estado";
            BaseDatos.bd().MoverTabla("AccionesHoy", "HistoricoAcciones", campos);
            BaseDatos.bd().borrarTabla("AccionesHoy");
            tablaAcciones.borrarTabla();
        } catch (Exception e) {
            ServidorMain.log.severe("ERROR al limpiar tablas por Cambio de turno");
        }
    }

    public void resucitarCandidatos() {
        if (resucitador == null) {
            return;
        }
        ArrayList<RegistroCandidatos> lista = resucitador.resucitables();
        if (lista != null) {
            for (RegistroCandidatos rc : lista) {
                if (rc != null) {
                    tablaCandidatos.resucitar(rc, gestores);
                    Global.msjCondNormal("- Resucitado ", rc.getId(), " ", rc.getNif(), " ", rc.getNumeroTicket());
                }
            }
            Global.msjCondNormal("- Quedan ", resucitador.numeroPendientes(), " por resucitar");
        }
    }

    public void apagaOperadoresFantasma() {
        long ahora = System.currentTimeMillis();
        ArrayList<Operador> borrables = new ArrayList<>();
        for (Operador op : operadores) {
            if (op == null) {   // no me explico como podria ocurrir esto, pero por si...
                continue;
            }
            ClientHandler handler = op.getHandler();
            if (handler == null) {   // ni esto tampoco, pero parece que ocurre...
                borrables.add(op);
                continue;
            }
            Date hora = handler.getLastCommunicationTime();
            if (hora == null) {
                continue;
            }
            long ultimaComunicacion = hora.getTime();
            if (ahora - ultimaComunicacion > TIMEOUT_FANTASMA) {
                borrables.add(op);
            }
        }
        for (Operador borrable : borrables) {
            try {
                MensajeInterno mensaje = new MensajeInterno(MensajeInterno.MSG_CIERRE_CLIENTE + "", null);
                Operador op = borrable;
                if (conf.modoVerboso) {
                    System.out.println("Apagado operador fantasma: " + op.getNombre() + ", mesa " + op.getPuesto());
                }
                mensaje.clienteTratado = op;
                GestorColas.getGestor().encola(mensaje);
                try {
                    op.enviaMensajeUrgente(Operador.MSG_CIERRE_URGENTE, "Presunta desconexión");
                } catch (Exception exc) {
                    ServidorMain.log.log(Level.WARNING, "ERROR en OperadoresFantasmas al enviar mensaje de cierre a {0}", op.toString());
                }
            }catch (Exception exc) {
                ServidorMain.log.log(Level.WARNING, "ERROR al borrar Operador Fantasma\n{0}", exc.getMessage());
            }
        }
    }

    public void estimarTiempos() {
        try {
            estimador.estimar(tablaPresentes);
            tablaPresentes.principio();
            StringBuilder regs = new StringBuilder(20 * tablaPresentes.count());
            while (tablaPresentes.haySiguiente()) {
                RegistroCandidatos reg = (RegistroCandidatos) tablaPresentes.siguiente();
                regs.append(reg.getId()).append('|').append(reg.getHoraEstimada()).append('·');
            }
            if (regs.length() > 0) {
                regs.setLength(regs.length() - 1);
                gestores.enviarEstimacionCandidatos(regs.toString());
            }
        } catch (Exception exc) {
            ServidorMain.log.log(Level.WARNING, "ERROR en estimaci\u00f3n de tiques\n{0}", exc.getMessage());
        }
    }

    public void salvarDatos(String path) {
        tablaCandidatos.salvarDatos(path);
    }

    public void despierta() {
        BaseDatos.bd().despertar();
    }

//</editor-fold>
}
