-- ============================================
-- 0. EXTENSIONES
-- ============================================
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
-- ============================================
-- 2. TABLA PERSONAS (SIMPLIFICADA)
-- ============================================
CREATE TABLE personas (
    id              BIGSERIAL PRIMARY KEY,
    nombres         VARCHAR(100) NOT NULL,
    apellidos       VARCHAR(100) NOT NULL,
    dui             VARCHAR(12) UNIQUE,
    nit             VARCHAR(17) UNIQUE,
    fecha_nacimiento DATE,
    genero          CHAR(1) CHECK (genero IN ('M', 'F', 'O')),
    email           VARCHAR(255),
    telefono        VARCHAR(20),
    direccion       TEXT,
    foto_url        VARCHAR(255),
    activo          BOOLEAN DEFAULT true,
    creado_en       TIMESTAMPTZ DEFAULT now(),
    actualizado_en  TIMESTAMPTZ DEFAULT now()
);

COMMENT ON TABLE personas IS 'Almacena información personal de todas las personas del sistema';

-- Índices básicos
CREATE INDEX idx_personas_activas ON personas(activo) WHERE activo = true;
CREATE INDEX idx_personas_nombre ON personas(nombres, apellidos);

-- ============================================
-- 3. TABLA NIÑOS (SIMPLIFICADA)
-- ============================================
CREATE TABLE ninos (
    id                  BIGSERIAL PRIMARY KEY,
    persona_id          BIGINT NOT NULL UNIQUE REFERENCES personas(id),
    fecha_inscripcion   DATE NOT NULL DEFAULT CURRENT_DATE,
    codigo_inscripcion  VARCHAR(20) UNIQUE NOT NULL,
    estado              VARCHAR(20) DEFAULT 'ACTIVO' 
        CHECK (estado IN ('ACTIVO', 'INACTIVO', 'GRADUADO')),
    nivel_grado         VARCHAR(20),
    alergias            TEXT,
    contacto_emergencia_nombre VARCHAR(100),
    contacto_emergencia_telefono VARCHAR(20),
    activo              BOOLEAN DEFAULT true,
    creado_en           TIMESTAMPTZ DEFAULT now(),
    actualizado_en      TIMESTAMPTZ DEFAULT now()
);

COMMENT ON TABLE ninos IS 'Niños registrados en la fundación con información específica';

-- Índices básicos
CREATE INDEX idx_ninos_estado ON ninos(estado);
CREATE INDEX idx_ninos_activos ON ninos(activo) WHERE activo = true;

-- ============================================
-- 4. TABLA ENCARGADOS (SIMPLIFICADA)
-- ============================================
CREATE TABLE encargados_nino (
    id                  BIGSERIAL PRIMARY KEY,
    nino_id             BIGINT NOT NULL REFERENCES ninos(id),
    persona_id          BIGINT NOT NULL REFERENCES personas(id),
    parentesco          VARCHAR(50) NOT NULL DEFAULT 'PADRE'
        CHECK (parentesco IN ('PADRE', 'MADRE', 'ENCARGADO', 'ABUELO')),
    es_principal        BOOLEAN DEFAULT false,
    puede_recoger       BOOLEAN DEFAULT true,
    creado_en           TIMESTAMPTZ DEFAULT now()
);

-- Restricción para un solo encargado principal por niño
CREATE UNIQUE INDEX uq_encargado_principal_nino 
ON encargados_nino(nino_id) 
WHERE es_principal = true;

-- Índices básicos
CREATE INDEX idx_encargados_nino ON encargados_nino(nino_id);
CREATE INDEX idx_encargados_persona ON encargados_nino(persona_id);

-- ============================================
-- 5. TABLA ASISTENCIA (SIMPLIFICADA)
-- ============================================
CREATE TABLE asistencia (
    id              BIGSERIAL PRIMARY KEY,
    nino_id         BIGINT NOT NULL REFERENCES ninos(id),
    fecha           DATE NOT NULL DEFAULT CURRENT_DATE,
    estado          VARCHAR(20) NOT NULL DEFAULT 'PRESENTE'
        CHECK (estado IN ('PRESENTE', 'AUSENTE', 'JUSTIFICADO', 'ENFERMO', 'TARDE')),
    hora_entrada    TIME,
    hora_salida     TIME,
    notas           TEXT,
    creado_en       TIMESTAMPTZ DEFAULT now()
);

COMMENT ON TABLE asistencia IS 'Registro diario de asistencia de los niños';

-- Índices básicos
CREATE INDEX idx_asistencia_fecha ON asistencia(fecha DESC);
CREATE INDEX idx_asistencia_nino_fecha ON asistencia(nino_id, fecha DESC);
CREATE UNIQUE INDEX uq_asistencia_nino_fecha ON asistencia(nino_id, fecha);
-- ============================================
-- 6. TABLA ANTECEDENTES (NUEVA)
-- ============================================
CREATE TABLE antecedentes_nino (
    id                  BIGSERIAL PRIMARY KEY,
    nino_id             BIGINT NOT NULL REFERENCES ninos(id),
    fecha_incidente     TIMESTAMPTZ NOT NULL DEFAULT now(),
    tipo_antecedente    VARCHAR(50) NOT NULL 
        CHECK (tipo_antecedente IN ('LlamadaAtencion', 'Suspension', 'Castigo')),
    descripcion         TEXT NOT NULL,
    gravedad            VARCHAR(20) NOT NULL
        CHECK (gravedad IN ('Alta', 'Media', 'Baja')),
    usuario_registra    VARCHAR(100) NOT NULL,
    activo              BOOLEAN DEFAULT true,
    creado_en           TIMESTAMPTZ DEFAULT now(),
    actualizado_en      TIMESTAMPTZ DEFAULT now()
);

COMMENT ON TABLE antecedentes_nino IS 'Historial de antecedentes, incidentes y sanciones de los niños';

CREATE INDEX idx_antecedentes_nino ON antecedentes_nino(nino_id);
CREATE INDEX idx_antecedentes_fecha ON antecedentes_nino(fecha_incidente DESC);

-- ============================================
-- 6. TABLA INVENTARIO (SIMPLIFICADA)
-- ============================================
CREATE TABLE categorias_inventario (
    id              SERIAL PRIMARY KEY,
    nombre          VARCHAR(100) NOT NULL,
    descripcion     TEXT,
    activo          BOOLEAN DEFAULT true,
    creado_en       TIMESTAMPTZ DEFAULT now()
);

CREATE TABLE items_inventario (
    id              BIGSERIAL PRIMARY KEY,
    categoria_id    INTEGER REFERENCES categorias_inventario(id),
    nombre          VARCHAR(200) NOT NULL,
    descripcion     TEXT,
    unidad          VARCHAR(20) DEFAULT 'UNIDAD',
    stock_actual    INTEGER DEFAULT 0,
    stock_minimo    INTEGER DEFAULT 0,
    ubicacion       VARCHAR(100),
    activo          BOOLEAN DEFAULT true,
    creado_en       TIMESTAMPTZ DEFAULT now(),
    actualizado_en  TIMESTAMPTZ DEFAULT now()
);

COMMENT ON TABLE items_inventario IS 'Items del inventario de la fundación';

-- Índices básicos
CREATE INDEX idx_items_inventario_categoria ON items_inventario(categoria_id);
CREATE INDEX idx_items_inventario_activos ON items_inventario(activo) WHERE activo = true;

-- ============================================
-- 7. TABLA CAJA CHICA (SIMPLIFICADA)
-- ============================================
CREATE TABLE categorias_gasto (
    id              SERIAL PRIMARY KEY,
    nombre          VARCHAR(100) NOT NULL,
    descripcion     TEXT,
    activo          BOOLEAN DEFAULT true,
    creado_en       TIMESTAMPTZ DEFAULT now()
);

CREATE TABLE transacciones_caja (
    id                  BIGSERIAL PRIMARY KEY,
    fecha_transaccion   DATE NOT NULL DEFAULT CURRENT_DATE,
    tipo_transaccion    VARCHAR(20) NOT NULL
        CHECK (tipo_transaccion IN ('INGRESO', 'EGRESO')),
    categoria_id        INTEGER REFERENCES categorias_gasto(id),
    monto               DECIMAL(12,2) NOT NULL CHECK (monto > 0),
    descripcion         VARCHAR(200) NOT NULL,
    referencia          VARCHAR(100),
    metodo_pago         VARCHAR(20) DEFAULT 'EFECTIVO'
        CHECK (metodo_pago IN ('EFECTIVO', 'CHEQUE', 'TRANSFERENCIA')),
    creado_en           TIMESTAMPTZ DEFAULT now()
);

COMMENT ON TABLE transacciones_caja IS 'Movimientos financieros de la fundación';

-- Índices básicos
CREATE INDEX idx_transacciones_fecha ON transacciones_caja(fecha_transaccion DESC);
CREATE INDEX idx_transacciones_tipo ON transacciones_caja(tipo_transaccion);

-- ============================================
-- 8. TABLA USUARIOS (SIMPLIFICADA)
-- ============================================
CREATE TABLE usuarios (
    id                  BIGSERIAL PRIMARY KEY,
    persona_id          BIGINT UNIQUE REFERENCES personas(id),
    nombre_usuario      VARCHAR(50) UNIQUE NOT NULL,
    email               VARCHAR(255) UNIQUE NOT NULL,
    hash_contrasena     TEXT NOT NULL,
    activo              BOOLEAN DEFAULT true,
    ultimo_login        TIMESTAMPTZ,
    creado_en           TIMESTAMPTZ DEFAULT now(),
    actualizado_en      TIMESTAMPTZ DEFAULT now()
);

COMMENT ON TABLE usuarios IS 'Usuarios del sistema con credenciales de acceso';

-- Índices básicos
CREATE INDEX idx_usuarios_activos ON usuarios(activo) WHERE activo = true;

-- ============================================
-- 9. TABLA ROLES (SIMPLIFICADA)
-- ============================================
CREATE TABLE roles_sistema (
    id              SERIAL PRIMARY KEY,
    codigo          VARCHAR(50) UNIQUE NOT NULL,
    nombre          VARCHAR(100) NOT NULL,
    descripcion     TEXT,
    creado_en       TIMESTAMPTZ DEFAULT now()
);

COMMENT ON TABLE roles_sistema IS 'Roles del sistema para control de acceso (RBAC)';

-- Roles básicos
INSERT INTO roles_sistema (codigo, nombre, descripcion) VALUES
('ADMIN', 'Administrador', 'Acceso completo al sistema'),
('MAESTRO', 'Maestro', 'Personal docente'),
('ENCARGADO', 'Encargado', 'Encargado de niños'),
('LECTOR', 'Solo Lectura', 'Acceso de solo lectura');

-- ============================================
-- 10. TABLA PERMISOS (SIMPLIFICADA)
-- ============================================
CREATE TABLE permisos (
    id              SERIAL PRIMARY KEY,
    modulo          VARCHAR(50) NOT NULL,
    codigo          VARCHAR(100) UNIQUE NOT NULL,
    nombre          VARCHAR(100) NOT NULL,
    descripcion     TEXT,
    creado_en       TIMESTAMPTZ DEFAULT now()
);

COMMENT ON TABLE permisos IS 'Permisos específicos del sistema organizados por módulos';

-- Permisos básicos
INSERT INTO permisos (modulo, codigo, nombre, descripcion) VALUES
('PERSONAS', 'personas.ver', 'Ver Personas', 'Ver lista de personas'),
('PERSONAS', 'personas.editar', 'Editar Personas', 'Editar información de personas'),
('NIÑOS', 'ninos.ver', 'Ver Niños', 'Ver lista de niños'),
('NIÑOS', 'ninos.editar', 'Editar Niños', 'Editar información de niños'),
('ASISTENCIA', 'asistencia.ver', 'Ver Asistencia', 'Ver registros de asistencia'),
('ASISTENCIA', 'asistencia.marcar', 'Marcar Asistencia', 'Registrar asistencia diaria'),
('INVENTARIO', 'inventario.ver', 'Ver Inventario', 'Ver items del inventario'),
('INVENTARIO', 'inventario.editar', 'Editar Inventario', 'Editar información de items'),
('CAJA', 'caja.ver', 'Ver Caja', 'Ver movimientos de caja'),
('CAJA', 'caja.editar', 'Editar Caja', 'Registrar movimientos de caja'),
('USUARIOS', 'usuarios.ver', 'Ver Usuarios', 'Ver lista de usuarios'),
('USUARIOS', 'usuarios.editar', 'Editar Usuarios', 'Editar información de usuarios');

-- ============================================
-- 11. TABLA ROLES-USUARIO (SIMPLIFICADA)
-- ============================================
CREATE TABLE roles_usuario (
    usuario_id      BIGINT NOT NULL REFERENCES usuarios(id),
    rol_id          INTEGER NOT NULL REFERENCES roles_sistema(id),
    asignado_en     TIMESTAMPTZ DEFAULT now(),
    PRIMARY KEY (usuario_id, rol_id)
);

-- ============================================
-- 12. TABLA AUDITORÍA (SIMPLIFICADA)
-- ============================================
CREATE TABLE registros_auditoria (
    id                  BIGSERIAL PRIMARY KEY,
    usuario_id          BIGINT REFERENCES usuarios(id),
    accion              VARCHAR(50) NOT NULL,
    entidad             VARCHAR(50) NOT NULL,
    entidad_id          BIGINT,
    descripcion         TEXT,
    creado_en           TIMESTAMPTZ NOT NULL DEFAULT now()
);

COMMENT ON TABLE registros_auditoria IS 'Registro de auditoría de todas las acciones importantes del sistema';

-- Índices básicos
CREATE INDEX idx_auditoria_fecha ON registros_auditoria(creado_en DESC);
CREATE INDEX idx_auditoria_usuario ON registros_auditoria(usuario_id);

-- ============================================
-- 13. DATOS INICIALES
-- ============================================
-- Insertar categorías de gasto básicas
INSERT INTO categorias_gasto (nombre, descripcion) VALUES
('ALIMENTACION', 'Gastos de alimentación'),
('EDUCACION', 'Material educativo'),
('MANTENIMIENTO', 'Mantenimiento de instalaciones'),
('SERVICIOS', 'Servicios públicos'),
('SALARIOS', 'Pago de salarios'),
('OTROS', 'Otros gastos');

-- Insertar categorías de inventario básicas
INSERT INTO categorias_inventario (nombre, descripcion) VALUES
('ALIMENTOS', 'Productos alimenticios'),
('MATERIAL_ESCOLAR', 'Material escolar y educativo'),
('LIMPIEZA', 'Productos de limpieza'),
('OFICINA', 'Material de oficina'),
('OTROS', 'Otras categorías');

-- ============================================
-- 14. USUARIO ADMIN POR DEFECTO
-- ============================================
DO $$
DECLARE
    persona_id BIGINT;
    usuario_id BIGINT;
BEGIN
    -- Crear persona admin
    INSERT INTO personas (
        nombres, 
        apellidos, 
        email, 
        telefono, 
        activo
    ) VALUES (
        'Administrador',
        'Sistema',
        'admin@fundacion.org',
        '0000-0000',
        true
    ) RETURNING id INTO persona_id;

    -- Crear usuario admin (contraseña: Admin123!)
    INSERT INTO usuarios (
        persona_id,
        nombre_usuario,
        email,
        hash_contrasena,
        activo
    ) VALUES (
        persona_id,
        'admin',
        'admin@fundacion.org',
        -- bcrypt hash para 'Admin123!'
        '$2a$12$Y8vjQJfT6V5UeH5qQ8qB3uK9zLmNpQrS2T3U4V5W6X7Y8Z9A0B1C2D3E4F5G6H7I8',
        true
    ) RETURNING id INTO usuario_id;

    -- Asignar rol de admin
    INSERT INTO roles_usuario (usuario_id, rol_id)
    SELECT usuario_id, id
    FROM roles_sistema 
    WHERE codigo = 'ADMIN';
END
$$;

-- ============================================
-- 15. FUNCIÓN PARA ACTUALIZAR TIMESTAMPS
-- ============================================
CREATE OR REPLACE FUNCTION actualizar_actualizado_en()
RETURNS TRIGGER AS $$
BEGIN
    NEW.actualizado_en = now();
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

-- Triggers para actualizar automáticamente
CREATE TRIGGER trg_personas_actualizado
    BEFORE UPDATE ON personas
    FOR EACH ROW
    EXECUTE FUNCTION actualizar_actualizado_en();

CREATE TRIGGER trg_ninos_actualizado
    BEFORE UPDATE ON ninos
    FOR EACH ROW
    EXECUTE FUNCTION actualizar_actualizado_en();

CREATE TRIGGER trg_items_inventario_actualizado
    BEFORE UPDATE ON items_inventario
    FOR EACH ROW
    EXECUTE FUNCTION actualizar_actualizado_en();

CREATE TRIGGER trg_usuarios_actualizado
    BEFORE UPDATE ON usuarios
    FOR EACH ROW
    EXECUTE FUNCTION actualizar_actualizado_en();

-- ============================================
-- 16. VISTAS ÚTILES
-- ============================================
-- Vista para información básica de niños
CREATE VIEW vista_ninos_completa AS
SELECT 
    n.id,
    n.codigo_inscripcion,
    p.nombres,
    p.apellidos,
    p.fecha_nacimiento,
    EXTRACT(YEAR FROM AGE(p.fecha_nacimiento)) as edad,
    p.genero,
    n.estado,
    n.nivel_grado,
    n.fecha_inscripcion,
    n.contacto_emergencia_nombre,
    n.contacto_emergencia_telefono
FROM ninos n
JOIN personas p ON n.persona_id = p.id
WHERE n.activo = true AND p.activo = true;

-- Vista para asistencia del mes actual
CREATE VIEW vista_asistencia_mensual AS
SELECT 
    nino_id,
    DATE_TRUNC('month', fecha) as mes,
    COUNT(*) as total_dias,
    COUNT(*) FILTER (WHERE estado = 'PRESENTE') as dias_presente,
    COUNT(*) FILTER (WHERE estado = 'AUSENTE') as dias_ausente,
    ROUND(
        (COUNT(*) FILTER (WHERE estado = 'PRESENTE') * 100.0 / 
        NULLIF(COUNT(*), 0)
    ), 2) as porcentaje_asistencia
FROM asistencia
GROUP BY nino_id, DATE_TRUNC('month', fecha);

-- Vista para inventario bajo stock
CREATE VIEW vista_inventario_bajo_stock AS
SELECT 
    i.id,
    i.nombre,
    c.nombre as categoria,
    i.unidad,
    i.stock_actual,
    i.stock_minimo,
    CASE 
        WHEN i.stock_actual <= 0 THEN 'AGOTADO'
        WHEN i.stock_actual <= i.stock_minimo THEN 'BAJO'
        ELSE 'SUFICIENTE'
    END as estado_stock
FROM items_inventario i
JOIN categorias_inventario c ON i.categoria_id = c.id
WHERE i.activo = true AND (i.stock_actual <= i.stock_minimo OR i.stock_actual <= 0)
ORDER BY i.stock_actual ASC;



-- ============================================
-- 17. TABLA CONFIGURACIÓN DEL SISTEMA (DINÁMICA)
-- ============================================
CREATE TABLE configuracion_sistema (
    id                  SERIAL PRIMARY KEY,
    clave               VARCHAR(100) UNIQUE NOT NULL,
    valor               TEXT,
    tipo_dato           VARCHAR(20) DEFAULT 'TEXTO'
        CHECK (tipo_dato IN ('TEXTO', 'NUMERO', 'BOOLEANO', 'FECHA', 'JSON', 'HTML')),
    categoria           VARCHAR(50) DEFAULT 'GENERAL',
    grupo               VARCHAR(50) DEFAULT 'SISTEMA',
    descripcion         TEXT,
    es_editable         BOOLEAN DEFAULT true,
    es_publico          BOOLEAN DEFAULT false,
    orden               INTEGER DEFAULT 0,
    opciones            JSONB, -- Para campos con opciones predefinidas
    validacion_regex    VARCHAR(200), -- Expresión regular para validación
    creado_en           TIMESTAMPTZ DEFAULT now(),
    actualizado_en      TIMESTAMPTZ DEFAULT now()
);

COMMENT ON TABLE configuracion_sistema IS 'Configuración dinámica del sistema - clave-valor extensible';

-- Índices para búsqueda eficiente
CREATE INDEX idx_configuracion_clave ON configuracion_sistema(clave);
CREATE INDEX idx_configuracion_categoria ON configuracion_sistema(categoria);
CREATE INDEX idx_configuracion_grupo ON configuracion_sistema(grupo);

-- ============================================
-- 18. VISTAS PARA CONFIGURACIÓN
-- ============================================
-- Vista para obtener configuración organizada
CREATE VIEW vista_configuracion_organizada AS
SELECT 
    id,
    clave,
    valor,
    tipo_dato,
    categoria,
    grupo,
    descripcion,
    es_editable,
    es_publico,
    orden,
    opciones,
    validacion_regex,
    creado_en,
    actualizado_en
FROM configuracion_sistema
ORDER BY categoria, grupo, orden, clave;

-- Vista para configuración pública (para APIs o frontend)
CREATE VIEW vista_configuracion_publica AS
SELECT 
    clave,
    valor,
    tipo_dato,
    categoria,
    grupo,
    descripcion
FROM configuracion_sistema
WHERE es_publico = true
ORDER BY categoria, grupo, orden, clave;

-- ============================================
-- 19. FUNCIONES ÚTILES PARA CONFIGURACIÓN
-- ============================================
-- Función para obtener un valor de configuración con tipo correcto
CREATE OR REPLACE FUNCTION obtener_configuracion(
    p_clave VARCHAR(100),
    p_valor_default TEXT DEFAULT NULL
)
RETURNS TEXT AS $$
DECLARE
    v_valor TEXT;
BEGIN
    SELECT valor INTO v_valor
    FROM configuracion_sistema
    WHERE clave = p_clave;
    
    RETURN COALESCE(v_valor, p_valor_default);
END;
$$ LANGUAGE plpgsql;

-- Función para establecer configuración (inserta o actualiza)
CREATE OR REPLACE FUNCTION establecer_configuracion(
    p_clave VARCHAR(100),
    p_valor TEXT,
    p_tipo_dato VARCHAR(20) DEFAULT 'TEXTO',
    p_categoria VARCHAR(50) DEFAULT 'GENERAL',
    p_grupo VARCHAR(50) DEFAULT 'SISTEMA',
    p_descripcion TEXT DEFAULT NULL,
    p_es_editable BOOLEAN DEFAULT true,
    p_es_publico BOOLEAN DEFAULT false,
    p_orden INTEGER DEFAULT 0
)
RETURNS VOID AS $$
BEGIN
    INSERT INTO configuracion_sistema (
        clave, valor, tipo_dato, categoria, grupo, 
        descripcion, es_editable, es_publico, orden
    ) VALUES (
        p_clave, p_valor, p_tipo_dato, p_categoria, p_grupo,
        p_descripcion, p_es_editable, p_es_publico, p_orden
    )
    ON CONFLICT (clave) DO UPDATE SET
        valor = EXCLUDED.valor,
        tipo_dato = EXCLUDED.tipo_dato,
        categoria = EXCLUDED.categoria,
        grupo = EXCLUDED.grupo,
        descripcion = COALESCE(EXCLUDED.descripcion, configuracion_sistema.descripcion),
        es_editable = EXCLUDED.es_editable,
        es_publico = EXCLUDED.es_publico,
        orden = EXCLUDED.orden,
        actualizado_en = now();
END;
$$ LANGUAGE plpgsql;

-- Función para obtener configuración por categoría
CREATE OR REPLACE FUNCTION obtener_configuracion_categoria(
    p_categoria VARCHAR(50)
)
RETURNS TABLE(
    clave VARCHAR(100),
    valor TEXT,
    tipo_dato VARCHAR(20),
    grupo VARCHAR(50),
    descripcion TEXT
) AS $$
BEGIN
    RETURN QUERY
    SELECT 
        cs.clave,
        cs.valor,
        cs.tipo_dato,
        cs.grupo,
        cs.descripcion
    FROM configuracion_sistema cs
    WHERE cs.categoria = p_categoria
    ORDER BY cs.grupo, cs.orden, cs.clave;
END;
$$ LANGUAGE plpgsql;

-- ============================================
-- 20. TRIGGER PARA ACTUALIZAR TIMESTAMP
-- ============================================
CREATE TRIGGER trg_configuracion_actualizado
    BEFORE UPDATE ON configuracion_sistema
    FOR EACH ROW
    EXECUTE FUNCTION actualizar_actualizado_en();

-- ============================================
-- 21. FUNCIÓN PARA GENERAR CÓDIGO DE INSCRIPCIÓN
-- ============================================
CREATE OR REPLACE FUNCTION generar_codigo_inscripcion(
    p_apellidos TEXT,
    p_anio SMALLINT
)
RETURNS VARCHAR(20) AS $$
DECLARE
    v_iniciales TEXT := '';
    v_anio TEXT := LPAD((p_anio % 100)::TEXT, 2, '0');
    v_correlativo INTEGER;
    v_ultimo_codigo TEXT;
    v_parte TEXT;
BEGIN
    -- Validación básica
    IF p_anio < 2000 OR p_anio > 2100 THEN
        RAISE EXCEPTION 'Año inválido: %', p_anio;
    END IF;

    -- Iniciales de apellidos
    FOR v_parte IN SELECT unnest(string_to_array(p_apellidos, ' ')) LOOP
        IF v_parte <> '' THEN
            v_iniciales := v_iniciales || UPPER(LEFT(v_parte, 1));
        END IF;
    END LOOP;

    -- Último correlativo por inicial + año
    SELECT codigo_inscripcion
    INTO v_ultimo_codigo
    FROM ninos
    WHERE codigo_inscripcion LIKE v_iniciales || v_anio || '___'
    ORDER BY codigo_inscripcion DESC
    LIMIT 1;

    IF v_ultimo_codigo IS NOT NULL THEN
        v_correlativo := RIGHT(v_ultimo_codigo, 3)::INTEGER + 1;
    ELSE
        v_correlativo := 1;
    END IF;

    RETURN v_iniciales || v_anio || LPAD(v_correlativo::TEXT, 3, '0');
END;
$$ LANGUAGE plpgsql;
-- ============================================================================
-- MOTOR DE BÚSQUEDA AVANZADO PARA PERSONAS
-- ============================================================================
-- Características:
-- ✅ Normalización (acentos, mayúsculas)
-- ✅ Tokenización (búsqueda por palabras individuales)
-- ✅ Fuzzy matching (errores ortográficos)
-- ✅ Ranking ponderado (diferentes campos, diferentes pesos)
-- ✅ Búsqueda en dos fases (índice + CPU)
-- ============================================================================

-- 1️⃣ Asegurar que las extensiones estén activas
CREATE EXTENSION IF NOT EXISTS pg_trgm;
CREATE EXTENSION IF NOT EXISTS fuzzystrmatch;
CREATE EXTENSION IF NOT EXISTS unaccent;

-- ============================================================================
-- 2️⃣ FUNCIÓN AUXILIAR: Normalizar texto
-- ============================================================================
CREATE OR REPLACE FUNCTION normalizar_texto(texto TEXT)
RETURNS TEXT AS $$
BEGIN
    RETURN unaccent(lower(trim(texto)));
END;
$$ LANGUAGE plpgsql IMMUTABLE;

-- ============================================================================
-- 3️⃣ FUNCIÓN PRINCIPAL: Búsqueda avanzada
-- ============================================================================
CREATE OR REPLACE FUNCTION buscar_personas_v2(
    p_termino TEXT,
    p_tipo TEXT,
    p_limite INTEGER DEFAULT 10
)
RETURNS TABLE(
    id BIGINT,
    text TEXT,
    score DOUBLE PRECISION,
    match_type TEXT  -- Para debugging: qué tipo de match fue
) AS $$
DECLARE
    v_termino_norm TEXT;
    v_tokens TEXT[];
    v_sim_threshold DOUBLE PRECISION := 0.15;  -- Más permisivo
    v_lev_threshold INTEGER := 3;              -- Permite más errores
    
    -- Pesos para ranking (ajusta según importancia)
    v_peso_nombre DOUBLE PRECISION := 1.0;
    v_peso_apellido DOUBLE PRECISION := 0.8;
    v_peso_codigo DOUBLE PRECISION := 0.6;
    v_peso_dui DOUBLE PRECISION := 0.5;
BEGIN
    -- Normalizar el término de búsqueda
    v_termino_norm := normalizar_texto(p_termino);
    
    -- Tokenizar (separar por palabras)
    v_tokens := string_to_array(v_termino_norm, ' ');
    
    -- ========================================================================
    -- BÚSQUEDA PARA NIÑOS
    -- ========================================================================
    IF upper(p_tipo) = 'NINO' THEN
        RETURN QUERY
        WITH candidatos AS (
            -- FASE 1: Filtro rápido por índice (operador %)
            SELECT
                n.id,
                p.nombres,
                p.apellidos,
                n.codigo_inscripcion,
                normalizar_texto(p.nombres) AS nombres_norm,
                normalizar_texto(p.apellidos) AS apellidos_norm,
                normalizar_texto(n.codigo_inscripcion) AS codigo_norm
            FROM ninos n
            JOIN personas p ON p.id = n.persona_id
            WHERE n.activo = true
              AND p.activo = true
              AND (
                    -- Búsqueda rápida con operador % (usa índice GIN)
                    normalizar_texto(p.nombres || ' ' || p.apellidos) % v_termino_norm
                 OR normalizar_texto(n.codigo_inscripcion) % v_termino_norm
                 OR normalizar_texto(p.nombres) % ANY(v_tokens)
                 OR normalizar_texto(p.apellidos) % ANY(v_tokens)
              )
        ),
        scoring AS (
            -- FASE 2: Cálculo preciso de scores
            SELECT
                c.id,
                (c.nombres || ' ' || c.apellidos || ' | Código: ' || c.codigo_inscripcion)::TEXT AS display_text,
                
                -- Score por similitud de nombre completo
                (similarity(c.nombres_norm || ' ' || c.apellidos_norm, v_termino_norm) * v_peso_nombre) AS score_nombre_completo,
                
                -- Score por similitud de nombres individuales
                (similarity(c.nombres_norm, v_termino_norm) * v_peso_nombre) AS score_nombres,
                
                -- Score por similitud de apellidos
                (similarity(c.apellidos_norm, v_termino_norm) * v_peso_apellido) AS score_apellidos,
                
                -- Score por código
                (similarity(c.codigo_norm, v_termino_norm) * v_peso_codigo) AS score_codigo,
                
                -- Score por Levenshtein (nombres)
                (1.0 - (levenshtein(c.nombres_norm, v_termino_norm)::DOUBLE PRECISION 
                    / GREATEST(length(c.nombres_norm), length(v_termino_norm), 1))) * v_peso_nombre AS score_lev_nombres,
                
                -- Score por tokens (cada palabra individual)
                (
                    SELECT COALESCE(MAX(
                        GREATEST(
                            similarity(c.nombres_norm, tok),
                            similarity(c.apellidos_norm, tok)
                        )
                    ), 0)
                    FROM unnest(v_tokens) tok
                ) * 0.9 AS score_tokens,
                
                -- Determinar tipo de match
                CASE
                    WHEN similarity(c.codigo_norm, v_termino_norm) >= v_sim_threshold THEN 'codigo'
                    WHEN similarity(c.nombres_norm || ' ' || c.apellidos_norm, v_termino_norm) >= v_sim_threshold THEN 'nombre_completo'
                    WHEN levenshtein(c.nombres_norm, v_termino_norm) <= v_lev_threshold THEN 'fuzzy'
                    ELSE 'token'
                END AS match_type
                
            FROM candidatos c
        )
        SELECT
            s.id,
            s.display_text,
            -- Score final: tomar el máximo de todos los métodos
            GREATEST(
                s.score_nombre_completo,
                s.score_nombres,
                s.score_apellidos,
                s.score_codigo,
                s.score_lev_nombres,
                s.score_tokens
            ) AS score,
            s.match_type
        FROM scoring s
        WHERE GREATEST(
                s.score_nombre_completo,
                s.score_nombres,
                s.score_apellidos,
                s.score_codigo,
                s.score_lev_nombres,
                s.score_tokens
              ) > 0.1  -- Umbral mínimo para aparecer en resultados
        ORDER BY score DESC
        LIMIT p_limite;
    
    -- ========================================================================
    -- BÚSQUEDA PARA ENCARGADOS
    -- ========================================================================
    ELSIF upper(p_tipo) = 'ENCARGADO' THEN
        RETURN QUERY
        WITH candidatos AS (
            SELECT
                p.id,
                p.nombres,
                p.apellidos,
                COALESCE(p.dui, '') AS dui,
                en.parentesco,
                normalizar_texto(p.nombres) AS nombres_norm,
                normalizar_texto(p.apellidos) AS apellidos_norm,
                normalizar_texto(COALESCE(p.dui, '')) AS dui_norm
            FROM encargados_nino en
            JOIN personas p ON p.id = en.persona_id
            WHERE p.activo = true
              AND (
                    normalizar_texto(p.nombres || ' ' || p.apellidos) % v_termino_norm
                 OR normalizar_texto(COALESCE(p.dui, '')) % v_termino_norm
                 OR normalizar_texto(p.nombres) % ANY(v_tokens)
                 OR normalizar_texto(p.apellidos) % ANY(v_tokens)
              )
        ),
        scoring AS (
            SELECT
                c.id,
                (c.nombres || ' ' || c.apellidos || ' (' || c.parentesco || ')')::TEXT AS display_text,
                
                (similarity(c.nombres_norm || ' ' || c.apellidos_norm, v_termino_norm) * v_peso_nombre) AS score_nombre_completo,
                (similarity(c.nombres_norm, v_termino_norm) * v_peso_nombre) AS score_nombres,
                (similarity(c.apellidos_norm, v_termino_norm) * v_peso_apellido) AS score_apellidos,
                (similarity(c.dui_norm, v_termino_norm) * v_peso_dui) AS score_dui,
                
                (1.0 - (levenshtein(c.nombres_norm, v_termino_norm)::DOUBLE PRECISION 
                    / GREATEST(length(c.nombres_norm), length(v_termino_norm), 1))) * v_peso_nombre AS score_lev_nombres,
                
                (
                    SELECT COALESCE(MAX(
                        GREATEST(
                            similarity(c.nombres_norm, tok),
                            similarity(c.apellidos_norm, tok)
                        )
                    ), 0)
                    FROM unnest(v_tokens) tok
                ) * 0.9 AS score_tokens,
                
                CASE
                    WHEN similarity(c.dui_norm, v_termino_norm) >= v_sim_threshold THEN 'dui'
                    WHEN similarity(c.nombres_norm || ' ' || c.apellidos_norm, v_termino_norm) >= v_sim_threshold THEN 'nombre_completo'
                    WHEN levenshtein(c.nombres_norm, v_termino_norm) <= v_lev_threshold THEN 'fuzzy'
                    ELSE 'token'
                END AS match_type
                
            FROM candidatos c
        )
        SELECT
            s.id,
            s.display_text,
            GREATEST(
                s.score_nombre_completo,
                s.score_nombres,
                s.score_apellidos,
                s.score_dui,
                s.score_lev_nombres,
                s.score_tokens
            ) AS score,
            s.match_type
        FROM scoring s
        WHERE GREATEST(
                s.score_nombre_completo,
                s.score_nombres,
                s.score_apellidos,
                s.score_dui,
                s.score_lev_nombres,
                s.score_tokens
              ) > 0.1
        ORDER BY score DESC
        LIMIT p_limite;
    
    ELSE
        RAISE EXCEPTION 'Tipo no soportado: %. Use NINO o ENCARGADO', p_tipo;
    END IF;
END;
$$ LANGUAGE plpgsql;

-- ============================================================================
-- 4️⃣ ÍNDICES RECOMENDADOS (CRÍTICO PARA PERFORMANCE)
-- ============================================================================

-- Para tabla personas
CREATE INDEX IF NOT EXISTS idx_personas_nombres_trgm 
ON personas USING gin(normalizar_texto(nombres) gin_trgm_ops);

CREATE INDEX IF NOT EXISTS idx_personas_apellidos_trgm 
ON personas USING gin(normalizar_texto(apellidos) gin_trgm_ops);

CREATE INDEX IF NOT EXISTS idx_personas_nombre_completo_trgm 
ON personas USING gin(normalizar_texto(nombres || ' ' || apellidos) gin_trgm_ops);

CREATE INDEX IF NOT EXISTS idx_personas_dui_trgm 
ON personas USING gin(normalizar_texto(dui) gin_trgm_ops) 
WHERE dui IS NOT NULL;

CREATE INDEX IF NOT EXISTS idx_personas_activo 
ON personas(activo) WHERE activo = true;

-- Para tabla ninos
CREATE INDEX IF NOT EXISTS idx_ninos_codigo_trgm 
ON ninos USING gin(normalizar_texto(codigo_inscripcion) gin_trgm_ops);

CREATE INDEX IF NOT EXISTS idx_ninos_activo 
ON ninos(activo) WHERE activo = true;

CREATE INDEX IF NOT EXISTS idx_ninos_persona_id 
ON ninos(persona_id);

-- Para tabla encargados_nino
CREATE INDEX IF NOT EXISTS idx_encargados_persona_id 
ON encargados_nino(persona_id);

-- ============================================================================
-- 5️⃣ EJEMPLOS DE USO
-- ============================================================================

/*
-- Búsqueda de niños con errores ortográficos
SELECT * FROM buscar_personas_v2('juan albero', 'NINO');  -- encuentra "Juan Alberto"
SELECT * FROM buscar_personas_v2('mria', 'NINO');         -- encuentra "María"
SELECT * FROM buscar_personas_v2('jose', 'NINO');         -- encuentra "José"

-- Búsqueda por código parcial
SELECT * FROM buscar_personas_v2('2024', 'NINO');

-- Búsqueda de encargados
SELECT * FROM buscar_personas_v2('ana garcia', 'ENCARGADO');
SELECT * FROM buscar_personas_v2('12345678', 'ENCARGADO');  -- por DUI

-- Con límite personalizado
SELECT * FROM buscar_personas_v2('juan', 'NINO', 20);
*/

-- ============================================================================
-- 6️⃣ ESTADÍSTICAS Y DEBUGGING
-- ============================================================================

-- Ver distribución de tipos de match
CREATE OR REPLACE FUNCTION analizar_busqueda(p_termino TEXT, p_tipo TEXT)
RETURNS TABLE(
    match_type TEXT,
    cantidad BIGINT,
    score_promedio DOUBLE PRECISION
) AS $$
BEGIN
    RETURN QUERY
    SELECT
        b.match_type,
        COUNT(*) AS cantidad,
        AVG(b.score) AS score_promedio
    FROM buscar_personas_v2(p_termino, p_tipo, 100) b
    GROUP BY b.match_type
    ORDER BY cantidad DESC;
END;
$$ LANGUAGE plpgsql;

-- Ejemplo: SELECT * FROM analizar_busqueda('juan', 'NINO');