#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
generar_docx_S5.py
==================
Script de compilación del documento APA 7 para la actividad Semana 5
de NRC-8773 Visión por Computador — UNIMINUTO.

Genera: Oviedo_Alexander_VC_Semana5.docx

Ejecutar desde el directorio de la actividad:
    python3 generar_docx_S5.py
"""

from docx import Document
from docx.shared import Inches, Pt, Cm, RGBColor
from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx.enum.style import WD_STYLE_TYPE
from docx.oxml.ns import qn
from docx.oxml import OxmlElement
import os, sys

# ============================================================
# CONFIGURACIÓN DEL DOCUMENTO
# ============================================================

TITULO_TRABAJO = "Red Neuronal Simple para Reconocimiento de Dígitos Manuscritos con TensorFlow"
SUBTITULO      = "Presentación de Diapositivas — Implementando una Red Neuronal"
INTEGRANTES    = [
    "Alexander Oviedo Fadul",
    "Maria Fernanda Ruiz Paipilla",
    "Neheman Samir Jaller Cerchiaro",
    "William David Obando Lopez",
]
DOCENTE      = "Félix Julián Gutiérrez Bernal"
CURSO        = "Visión por Computador"
NRC          = "NRC-8773"
SEMANA       = "Semana 5"
PROGRAMA     = "Especialización en Inteligencia Artificial"
UNIVERSIDAD  = "Corporación Universitaria Minuto de Dios – UNIMINUTO"
FECHA        = "Junio de 2026"
COLAB_URL    = "https://colab.research.google.com/drive/[ENLACE_DEL_GRUPO]"
SALIDA       = "Oviedo_Alexander_VC_Semana5.docx"


# ============================================================
# FUNCIONES AUXILIARES
# ============================================================

def set_margins(doc, top=2.54, bottom=2.54, left=2.54, right=2.54):
    """Establece márgenes APA 7 (2.54 cm en todos los lados)."""
    for section in doc.sections:
        section.top_margin    = Cm(top)
        section.bottom_margin = Cm(bottom)
        section.left_margin   = Cm(left)
        section.right_margin  = Cm(right)


def set_page_number(doc):
    """Agrega número de página en esquina superior derecha (APA 7)."""
    for section in doc.sections:
        header = section.header
        p = header.paragraphs[0] if header.paragraphs else header.add_paragraph()
        p.alignment = WD_ALIGN_PARAGRAPH.RIGHT
        run = p.add_run()
        # Número de página automático
        fldChar1 = OxmlElement('w:fldChar')
        fldChar1.set(qn('w:fldCharType'), 'begin')
        instrText = OxmlElement('w:instrText')
        instrText.text = 'PAGE'
        fldChar2 = OxmlElement('w:fldChar')
        fldChar2.set(qn('w:fldCharType'), 'end')
        run._r.append(fldChar1)
        run._r.append(instrText)
        run._r.append(fldChar2)
        run.font.name = 'Calibri'
        run.font.size = Pt(11)


def add_paragraph(doc, text, style='Normal', bold=False, indent=True,
                  align=WD_ALIGN_PARAGRAPH.LEFT, space_after=0):
    """Agrega párrafo con formato APA 7."""
    p = doc.add_paragraph(style=style)
    p.alignment = align
    p.paragraph_format.space_after = Pt(space_after)
    p.paragraph_format.line_spacing = Pt(23)  # ~doble interlineado a 11pt

    if indent:
        p.paragraph_format.first_line_indent = Cm(1.27)

    run = p.add_run(text)
    run.font.name = 'Calibri'
    run.font.size = Pt(11)
    run.bold = bold
    return p


def add_heading(doc, text, level=1):
    """Agrega título con formato APA 7 por nivel."""
    p = doc.add_heading(text, level=level)
    p.paragraph_format.space_before = Pt(12)
    p.paragraph_format.space_after  = Pt(0)
    p.paragraph_format.line_spacing = Pt(23)

    for run in p.runs:
        run.font.name  = 'Calibri'
        run.font.size  = Pt(11)
        run.bold       = True
        run.font.color.rgb = RGBColor(0, 0, 0)

    if level == 1:
        p.alignment = WD_ALIGN_PARAGRAPH.CENTER
    else:
        p.alignment = WD_ALIGN_PARAGRAPH.LEFT

    return p


def add_reference(doc, text):
    """Agrega una referencia APA con sangría francesa."""
    p = doc.add_paragraph(style='Normal')
    p.alignment = WD_ALIGN_PARAGRAPH.LEFT
    p.paragraph_format.line_spacing = Pt(23)
    p.paragraph_format.space_after  = Pt(0)
    # Sangría francesa: 1.27cm izquierda, -1.27cm primera línea
    p.paragraph_format.left_indent        = Cm(1.27)
    p.paragraph_format.first_line_indent  = Cm(-1.27)

    run = p.add_run(text)
    run.font.name = 'Calibri'
    run.font.size = Pt(11)
    return p


# ============================================================
# GENERACIÓN DEL DOCUMENTO
# ============================================================

def generar_documento():
    doc = Document()

    # Configurar estilo Normal base
    normal_style = doc.styles['Normal']
    normal_style.font.name = 'Calibri'
    normal_style.font.size = Pt(11)
    normal_style.paragraph_format.space_after = Pt(0)

    set_margins(doc)
    set_page_number(doc)

    # --------------------------------------------------------
    # PORTADA
    # --------------------------------------------------------
    for _ in range(6):
        doc.add_paragraph()

    # Título
    p_titulo = doc.add_paragraph()
    p_titulo.alignment = WD_ALIGN_PARAGRAPH.CENTER
    p_titulo.paragraph_format.line_spacing = Pt(23)
    r = p_titulo.add_run(TITULO_TRABAJO)
    r.bold = True
    r.font.name = 'Calibri'
    r.font.size = Pt(11)

    doc.add_paragraph()

    # Integrantes
    p_int = doc.add_paragraph()
    p_int.alignment = WD_ALIGN_PARAGRAPH.CENTER
    p_int.paragraph_format.line_spacing = Pt(23)
    r2 = p_int.add_run('\n'.join(INTEGRANTES))
    r2.font.name = 'Calibri'
    r2.font.size = Pt(11)

    doc.add_paragraph()

    # Datos del curso
    datos_portada = [
        f"{UNIVERSIDAD}",
        f"{PROGRAMA}",
        f"{NRC}: {CURSO}",
        f"Docente: {DOCENTE}",
        f"{SEMANA}",
        f"{FECHA}",
    ]
    for dato in datos_portada:
        p_d = doc.add_paragraph()
        p_d.alignment = WD_ALIGN_PARAGRAPH.CENTER
        p_d.paragraph_format.line_spacing = Pt(23)
        p_d.paragraph_format.space_after  = Pt(0)
        r_d = p_d.add_run(dato)
        r_d.font.name = 'Calibri'
        r_d.font.size = Pt(11)

    doc.add_page_break()

    # --------------------------------------------------------
    # SECCIÓN 1: TÍTULO Y ENLACE DE COLAB
    # --------------------------------------------------------
    add_heading(doc, TITULO_TRABAJO, level=1)
    doc.add_paragraph()

    # Enlace de Google Colab (destacado)
    p_colab = doc.add_paragraph()
    p_colab.alignment = WD_ALIGN_PARAGRAPH.LEFT
    p_colab.paragraph_format.space_before = Pt(6)
    p_colab.paragraph_format.space_after  = Pt(6)
    p_colab.paragraph_format.line_spacing = Pt(23)
    r_label = p_colab.add_run("Enlace del notebook en Google Colab: ")
    r_label.bold = True
    r_label.font.name = 'Calibri'
    r_label.font.size = Pt(11)
    r_url = p_colab.add_run(COLAB_URL)
    r_url.font.name = 'Calibri'
    r_url.font.size = Pt(11)
    r_url.font.color.rgb = RGBColor(0x17, 0x5D, 0xDC)

    doc.add_paragraph()

    # --------------------------------------------------------
    # SECCIÓN 2: INTRODUCCIÓN
    # --------------------------------------------------------
    add_heading(doc, "Introducción", level=2)

    intro_1 = (
        "Desde finales de la década de 1940, la neurociencia computacional comenzó a plantear "
        "modelos matemáticos que buscaban replicar, de forma abstracta, el funcionamiento del cerebro "
        "biológico. La unidad fundamental de esos modelos es la neurona artificial: una unidad de "
        "procesamiento que recibe múltiples señales de entrada ponderadas por coeficientes llamados pesos "
        "(ω) y produce una única salida. Esta operación equivale matemáticamente a una regresión lineal "
        "con sesgo, tal como señala Ballard (2018) al describir las redes neuronales clásicas."
    )
    add_paragraph(doc, intro_1)

    intro_2 = (
        "La capacidad de una red neuronal para aprender patrones complejos surge de la combinación de "
        "este procesamiento lineal con las funciones de activación que introducen no linealidad. Sin ellas, "
        "apilar múltiples capas equivale a una sola transformación lineal. El algoritmo de retropropagación, "
        "combinado con el descenso de gradiente, ajusta iterativamente los pesos para minimizar el error "
        "entre las predicciones y los valores reales. Este ciclo de aprendizaje automático es, en esencia, "
        "la forma en que una máquina emula el proceso de aprendizaje por repetición y corrección del error."
    )
    add_paragraph(doc, intro_2)

    intro_3 = (
        "En el presente trabajo implementamos una red neuronal densa simple para el reconocimiento de "
        "dígitos manuscritos del 0 al 9 usando el framework TensorFlow y el dataset MNIST, siguiendo la "
        "metodología desarrollada por el docente Félix Julián Gutiérrez Bernal en la sesión sincrónica de "
        "la semana 5 del NRC-8773."
    )
    add_paragraph(doc, intro_3)

    # --------------------------------------------------------
    # SECCIÓN 3: FUNDAMENTOS TEÓRICOS
    # --------------------------------------------------------
    add_heading(doc, "Fundamentos Teóricos", level=2)

    add_heading(doc, "La Neurona Artificial", level=3)
    fund_1 = (
        "Una neurona artificial es una unidad de procesamiento inspirada en el cerebro biológico que "
        "recibe n entradas (x₁, x₂, ..., xₙ) ponderadas por pesos (ω₁, ω₂, ..., ωₙ), calcula su "
        "combinación lineal y aplica una función de activación para producir una salida. Dadhich (2018) "
        "describe cómo las imágenes digitales, al ser fundamentalmente matrices numéricas de píxeles con "
        "valores entre 0 y 255, se convierten directamente en el vector de entrada de la red."
    )
    add_paragraph(doc, fund_1)

    add_heading(doc, "Función de Activación y No Linealidad", level=3)
    fund_2 = (
        "La función de activación transforma la salida lineal de una neurona en una señal no lineal, "
        "permitiendo que la red aprenda patrones complejos. En nuestro modelo usamos dos funciones: "
        "ReLU (Rectified Linear Unit) en la capa oculta, que devuelve el valor si es positivo y 0 en caso "
        "contrario; y Softmax en la capa de salida, que convierte los valores en una distribución de "
        "probabilidades que suman 1 sobre las 10 clases posibles."
    )
    add_paragraph(doc, fund_2)

    add_heading(doc, "Descenso de Gradiente y Retropropagación", level=3)
    fund_3 = (
        "El entrenamiento se realiza mediante la retropropagación del error: el modelo calcula la diferencia "
        "entre su predicción y la etiqueta real usando la función de pérdida (categorical crossentropy), "
        "computa el gradiente de ese error respecto a cada peso mediante la regla de la cadena, y ajusta "
        "los pesos en la dirección opuesta al gradiente con el optimizador Adam. Este proceso se repite "
        "en lotes (mini-batches) de 100 imágenes durante 5 épocas."
    )
    add_paragraph(doc, fund_3)

    # --------------------------------------------------------
    # SECCIÓN 4: DESARROLLO (IMPLEMENTACIÓN)
    # --------------------------------------------------------
    add_heading(doc, "Desarrollo — Implementación con TensorFlow", level=2)

    add_heading(doc, "Importación del Dataset MNIST", level=3)
    dev_1 = (
        "El dataset MNIST contiene 70,000 imágenes de dígitos manuscritos: 60,000 para entrenamiento y "
        "10,000 para prueba. Cada imagen tiene dimensiones de 28×28 píxeles en escala de grises, "
        "representada como una matriz de 784 valores enteros entre 0 (negro) y 255 (blanco). Keras "
        "incluye MNIST de forma nativa mediante la instrucción: "
        "(X_train, Y_train), (X_test, Y_test) = mnist.load_data()."
    )
    add_paragraph(doc, dev_1)

    add_heading(doc, "Preprocesamiento de los Datos", level=3)
    dev_2 = (
        "Antes de alimentar las imágenes a la red se realizaron dos transformaciones esenciales. "
        "Primero, el reshape agrega una dimensión de canal (28×28 → 28×28×1) y la normalización "
        "divide los valores de píxel por 255 para llevarlos al rango [0, 1], mejorando la estabilidad "
        "numérica del entrenamiento. Segundo, se aplicó one-hot encoding a las etiquetas usando "
        "to_categorical: el dígito 3 se convierte, por ejemplo, en el vector [0,0,0,1,0,0,0,0,0,0], "
        "lo que permite compararlo directamente con la salida softmax del modelo (Ballard, 2018)."
    )
    add_paragraph(doc, dev_2)

    add_heading(doc, "Arquitectura de la Red Neuronal", level=3)
    dev_3 = (
        "El modelo secuencial implementado en Keras consta de tres capas. La primera es Flatten, que "
        "convierte la imagen 28×28×1 en un vector de 784 elementos (proceso que el docente describió "
        "en clase como 'llevar una imagen bidimensional a una única dimensión'). La segunda es una capa "
        "Dense con 128 neuronas y activación ReLU: aprende representaciones intermedias de los patrones "
        "visuales de los dígitos. La tercera es la capa de salida Dense con 10 neuronas y activación "
        "Softmax: produce una distribución de probabilidades sobre los 10 dígitos posibles. El total de "
        "parámetros entrenables es de 101,770."
    )
    add_paragraph(doc, dev_3)

    add_heading(doc, "Justificación de la Arquitectura", level=3)
    dev_4 = (
        "La elección de 128 neuronas en la capa oculta responde a un equilibrio bien establecido en la "
        "literatura: provee suficiente capacidad representacional para capturar las características visuales "
        "de los dígitos (curvas, líneas, bucles) sin generar sobreajuste en un dataset de tamaño moderado. "
        "Ballard (2018) señala que el número óptimo de unidades es un hiperparámetro a ajustar "
        "experimentalmente; 128 es el punto de partida estándar para MNIST. El uso de una sola capa "
        "oculta es suficiente para este problema, dado que las imágenes MNIST son relativamente simples "
        "y el objetivo es didáctico: sentar las bases conceptuales antes de avanzar a las redes "
        "convolucionales de la siguiente semana."
    )
    add_paragraph(doc, dev_4)

    add_heading(doc, "Configuración de Tensores y Entrenamiento", level=3)
    dev_5 = (
        "El modelo se compiló con el optimizador Adam (variante del descenso de gradiente con tasa "
        "de aprendizaje adaptativa), la función de pérdida categorical_crossentropy (apropiada para "
        "clasificación multiclase con etiquetas one-hot) y la métrica accuracy. El entrenamiento se "
        "ejecutó durante 5 épocas con mini-batches de 100 imágenes, utilizando el conjunto de prueba "
        "como datos de validación en cada época."
    )
    add_paragraph(doc, dev_5)

    # --------------------------------------------------------
    # SECCIÓN 5: RESULTADOS
    # --------------------------------------------------------
    add_heading(doc, "Resultados Obtenidos", level=2)

    res_1 = (
        "El modelo alcanzó una exactitud de validación de aproximadamente 97.47% al finalizar la quinta "
        "época. Los resultados por época evidencian una convergencia rápida: la exactitud pasó del 90.97% "
        "en la primera época al 98.15% en entrenamiento, con una pérdida que descendió de 0.3310 a 0.0653. "
        "La diferencia entre la exactitud de entrenamiento y la de validación es mínima (~0.7 puntos "
        "porcentuales), lo que indica que el modelo generalizó correctamente sin sobreajuste."
    )
    add_paragraph(doc, res_1)

    res_2 = (
        "De las 10,000 imágenes del conjunto de prueba, el modelo clasificó correctamente "
        "aproximadamente 9,747, cometiendo errores en 253 casos. Los errores más frecuentes ocurren "
        "entre dígitos con formas visualmente similares (4 y 9, o 3 y 8), lo cual es esperable en una "
        "red densa que no aprovecha la estructura espacial de la imagen."
    )
    add_paragraph(doc, res_2)

    # --------------------------------------------------------
    # SECCIÓN 6: ESTADO DE LA IA EN COLOMBIA
    # --------------------------------------------------------
    add_heading(doc, "Estado de la IA en Colombia", level=2)

    col_1 = (
        "Colombia avanza de manera progresiva en la adopción de la inteligencia artificial. El "
        "Documento CONPES 3975 de 2019 estableció la Política Nacional de Inteligencia Artificial, "
        "reconociendo que es una tecnología estratégica para el desarrollo socioeconómico del siglo XXI "
        "y comprometiendo al Estado en la formación de talento, el desarrollo de infraestructura digital "
        "y la generación de marcos regulatorios."
    )
    add_paragraph(doc, col_1)

    col_2 = (
        "En el sector público, iniciativas como el proyecto MARDUK en el Consejo Superior de la "
        "Judicatura han demostrado que las redes neuronales pueden automatizar tareas documentales "
        "complejas: reconocimiento de entidades en expedientes judiciales, clasificación de tutelas y "
        "análisis de tiempos procesales. Este tipo de aplicaciones —análogas en principio a lo que "
        "implementamos con MNIST— evidencian que el reconocimiento de patrones en datos no estructurados "
        "tiene un enorme potencial en el sector justicia colombiano."
    )
    add_paragraph(doc, col_2)

    col_3 = (
        "Sin embargo, persisten brechas importantes: la infraestructura computacional es limitada fuera "
        "de los grandes centros urbanos; la inversión en investigación y desarrollo como porcentaje del "
        "PIB sigue siendo baja; y la formación de profesionales especializados en IA apenas inicia en "
        "programas de posgrado como el que cursamos. El reto es que estas herramientas —hoy accesibles "
        "vía Google Colab sin costo— se democraticen realmente y no queden concentradas en las élites "
        "tecnológicas de unas pocas ciudades."
    )
    add_paragraph(doc, col_3)

    # --------------------------------------------------------
    # SECCIÓN 7: CONCLUSIONES
    # --------------------------------------------------------
    add_heading(doc, "Conclusiones", level=2)

    conclusiones = [
        ("El ejercicio de implementar una red neuronal simple con TensorFlow confirmó que el proceso "
         "de aprendizaje automático es, en esencia, la minimización iterativa de un error mediante el "
         "ajuste de parámetros: un proceso análogo —aunque abstracto— al aprendizaje por repetición y "
         "corrección del cerebro biológico."),
        ("El preprocesamiento de los datos (reshape, normalización y one-hot encoding) resultó "
         "ser tan determinante como la arquitectura del modelo: sin datos correctamente preparados, "
         "el entrenamiento no converge adecuadamente."),
        ("La arquitectura Flatten → Dense(128, ReLU) → Dense(10, Softmax) logró un ~97.5% de "
         "exactitud en el conjunto de prueba con solo 5 épocas, demostrando que redes relativamente "
         "simples son capaces de resolver problemas bien definidos con alta precisión."),
        ("El entrenamiento por mini-batches demostró ser eficiente: dividir 60,000 imágenes en "
         "lotes de 100 permitió entrenar en minutos en Google Colab, manteniendo buena capacidad "
         "de generalización."),
        ("Las redes densas presentan limitaciones intrínsecas para imágenes complejas, ya que no "
         "explotan la estructura espacial. Las redes convolucionales (CNN), tema de la siguiente "
         "semana, abordan precisamente esta limitación."),
        ("Colombia tiene el potencial para desarrollar IA aplicada a sus desafíos específicos "
         "(justicia, salud, agricultura), pero requiere inversión sostenida en talento humano, "
         "infraestructura y marcos regulatorios que garanticen un uso ético y equitativo."),
    ]

    for i, c in enumerate(conclusiones, 1):
        p = doc.add_paragraph(style='Normal')
        p.alignment = WD_ALIGN_PARAGRAPH.LEFT
        p.paragraph_format.line_spacing = Pt(23)
        p.paragraph_format.space_after  = Pt(0)
        p.paragraph_format.left_indent  = Cm(1.27)
        r = p.add_run(f"{i}. {c}")
        r.font.name = 'Calibri'
        r.font.size = Pt(11)

    # --------------------------------------------------------
    # REFERENCIAS
    # --------------------------------------------------------
    doc.add_paragraph()
    add_heading(doc, "Referencias", level=2)

    referencias = [
        ("Ballard, W. (2018). Hands-on deep learning for images with TensorFlow: Build intelligent "
         "computer vision applications using TensorFlow and Keras (Chapter 3, pp. 33–51). "
         "Packt Publishing."),
        ("Consejo Nacional de Política Económica y Social [CONPES]. (2019). Documento CONPES 3975: "
         "Política Nacional para la Transformación Digital e Inteligencia Artificial. "
         "Departamento Nacional de Planeación."),
        ("Dadhich, A. (2018). Practical computer vision: Extract insightful information from images "
         "using TensorFlow, Keras, and OpenCV (Chapter 2, pp. 23–38). Packt Publishing."),
        ("Google Colaboratory. (s.f.). Google Colab. https://colab.research.google.com/"),
        ("Quiñones Huatangari, L., Ochoa Toledo, L., Gamarra Torres, O., Bazán Correa, J., Delgado "
         "Soto, J., & Kemper Valverde, N. (2020). Red neuronal artificial para estimar un índice de "
         "calidad de agua. Enfoque UTE, 11(2), 113-124. "
         "https://doi.org/10.29019/enfoque.v11n2.633"),
        ("TensorFlow. (s.f.). TensorFlow documentation. https://www.tensorflow.org/"),
    ]

    for ref in referencias:
        add_reference(doc, ref)

    # --------------------------------------------------------
    # GUARDAR
    # --------------------------------------------------------
    doc.save(SALIDA)
    print(f"✅ Documento generado: {SALIDA}")
    print(f"   Integrantes: {', '.join(INTEGRANTES)}")
    print(f"   Docente: {DOCENTE}")
    print(f"   Semana: {SEMANA} | {NRC}")
    print(f"\n⚠️  Recuerde actualizar la URL de Google Colab en el documento.")
    print(f"   URL actual (placeholder): {COLAB_URL}")


if __name__ == '__main__':
    generar_documento()
