{"nbformat":4,"nbformat_minor":0,"metadata":{"colab":{"provenance":[],"authorship_tag":"ABX9TyM2WxeNxYZTesvTNA1FTTNh"},"kernelspec":{"name":"python3","display_name":"Python 3"},"language_info":{"name":"python"}},"cells":[{"cell_type":"markdown","source":["# **# Semana 1: Bases del texto y del dataset en ciberseguridad**\n","\n","### RdA1: Comprender los conceptos fundamentales del procesamiento de lenguaje natural para analizar información textual proveniente de incidentes de ciberseguridad, a través de técnicas de recolección, limpieza y análisis exploratorio de datos no estructurados.\n","\n","A continuación se presenta un código que automatiza los aspectos vistos de la semana 1, estos son:\n","1. Naturaleza del texto no estructurado en ciberseguridad: correos, tickets, logs narrativos.\n","2. Conceptos básicos de PLN: token, corpus, ruido, stopwords, n-gramas.\n","3. Características lingüísticas de amenazas: urgencia, suplantación, lenguaje persuasivo.\n","4. Fuentes de datos textuales en SOC/CSIRT.\n","5. Estructura del dataset: campos, metadatos y etiquetas.\n","6. Problemas éticos y de privacidad.\n","\n","**UTILIZACIÓN**\n","\n","Simplemente ejecuta el código.\n","\n","**RESULTADO**\n","\n","Una vez ejecutado, el código genera 3 archivos:\n","1. analisis_semana1.md : explica todo el análisis realizado.\n","2. dataset_prelimiar.csv : con el código listo para análisis.\n","3. protocolo_etico.md : con las consideraciones importantes.\n","\n"],"metadata":{"id":"qkNAy4G9aEH9"}},{"cell_type":"code","execution_count":null,"metadata":{"id":"kZjqqUziYz2U"},"outputs":[],"source":["from __future__ import annotations\n","\n","import csv\n","import json\n","import os\n","import re\n","from dataclasses import dataclass, asdict\n","from datetime import datetime\n","from typing import Dict, List, Optional, Tuple\n","\n","# -----------------------------\n","# Configuración básica\n","# -----------------------------\n","\n","# Stopwords (ES + algunas EN frecuentes en ciberseguridad)\n","STOPWORDS = set(\"\"\"\n","a al algo algunas algunos ante antes como con contra cual cuales cuando de del desde donde durante e el ella ellas\n","ellos en entre era erais eran eras eres es esa esas ese eso esos esta estabais estaban estabas estad estamos estan\n","estar estara estaran estare estareis estaremos estarias estariamos estarian estarias estas este estemos esto estos\n","estoy fin fue fuerais fueran fueras fueron fui fuimos forma ha habeis haber habia habiais habian habias habida\n","habidas habido habidos habiendo habla hablan hablas hasta hay haya hayais hayan hayas he hemos hicieron hace hacen\n","hacer haces hacia haciais hacian hacias hicieron hizo hoy i la las le les lo los mas me mi mia mias mientras mio\n","mios misma mismas mismo mismos muy nada ni no nos nosotras nosotros nuestra nuestras nuestro nuestros o os otra\n","otras otro otros para pero poco por porque que quien quienes se sea seais sean seas sera seran seras sere sereis\n","seremos serias seriais seriamos serian serias si sin sobre sois solo somos son soy su sus suya suyas suyo suyos\n","tal tales tambien te teneis tener tenia teniais tenian tenias tenido tengo tenga tengais tengan tengas tenia\n","tienen tienes toda todas todo todos tu tus un una unas unos u y ya\n","\n","the a an and or to of in on at for from by with without is are be been being this that these those your you we\n","our they their please urgent immediately now verify verification account password login credentials security IT\n","department\n","\"\"\".split())\n","\n","# Palabras/expresiones tipo ingeniería social (ES/EN)\n","URGENCIA = [\n"," r\"\\burgente\\b\", r\"\\binmediato\\b\", r\"\\binmediatamente\\b\", r\"\\bahora\\b\", r\"\\bya\\b\",\n"," r\"\\búltim[ao]\\b\", r\"\\bfinal\\b\", r\"\\b24\\s*horas\\b\", r\"\\ben\\s*\\d+\\s*minutos\\b\",\n"," r\"\\burgent\\b\", r\"\\bimmediate(ly)?\\b\", r\"\\basap\\b\"\n","]\n","AUTORIDAD = [\n"," r\"\\bdepartamento\\b\", r\"\\bseguridad\\b\", r\"\\bti\\b\", r\"\\bit\\b\", r\"\\badministraci[oó]n\\b\",\n"," r\"\\bsoporte\\b\", r\"\\bhelp\\s*desk\\b\", r\"\\bteam\\b\", r\"\\bsecurity\\b\", r\"\\badmin\\b\"\n","]\n","ACCION_IMPERATIVA = [\n"," r\"\\bconfirme\\b\", r\"\\bverifique\\b\", r\"\\bingrese\\b\", r\"\\bhaga\\s*clic\\b\", r\"\\bactualice\\b\",\n"," r\"\\bdescargue\\b\", r\"\\bresponda\\b\", r\"\\benvíe\\b\", r\"\\bproporcione\\b\",\n"," r\"\\bconfirm\\b\", r\"\\bverify\\b\", r\"\\bclick\\b\", r\"\\bupdate\\b\", r\"\\bsend\\b\"\n","]\n","\n","# Patrones técnicos típicos\n","RE_URL = re.compile(r\"(https?://[^\\s]+)\", re.IGNORECASE)\n","RE_EMAIL = re.compile(r\"\\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,}\\b\", re.IGNORECASE)\n","RE_IP = re.compile(r\"\\b(?:(?:25[0-5]|2[0-4]\\d|[01]?\\d?\\d)\\.){3}(?:25[0-5]|2[0-4]\\d|[01]?\\d?\\d)\\b\")\n","RE_HASH = re.compile(r\"\\b[a-fA-F0-9]{32}\\b|\\b[a-fA-F0-9]{40}\\b|\\b[a-fA-F0-9]{64}\\b\")\n","\n","# Ruido frecuente en correos/tickets\n","RE_SIGNATURE = re.compile(r\"(?is)(atentamente|saludos|best regards|regards).*$\")\n","RE_DISCLAIMER = re.compile(r\"(?is)(este mensaje|confidencialidad|disclaimer|privileged|confidential).*$\")\n","RE_HTML_TAG = re.compile(r\"<[^>]+>\")\n","\n","# Anonimización (placeholder)\n","ANON_MAP_PATTERNS = [\n"," (RE_EMAIL, \"[EMAIL]\"),\n"," (RE_IP, \"[IP]\"),\n"," (RE_URL, \"[URL]\"),\n"," (RE_HASH, \"[HASH]\"),\n","]\n","\n","# -----------------------------\n","# Utilidades NLP mínimas\n","# -----------------------------\n","\n","def normalize_text(text: str) -> str:\n"," t = text.strip()\n"," t = RE_HTML_TAG.sub(\" \", t)\n"," t = re.sub(r\"\\s+\", \" \", t)\n"," return t\n","\n","def tokenize(text: str) -> List[str]:\n"," # Tokenización simple (palabras + números). Para curso intro está bien.\n"," return re.findall(r\"[A-Za-zÁÉÍÓÚÑáéíóúñ]+|\\d+\", text)\n","\n","def remove_stopwords(tokens: List[str]) -> List[str]:\n"," return [tok for tok in tokens if tok.lower() not in STOPWORDS]\n","\n","def ngrams(tokens: List[str], n: int) -> List[str]:\n"," if n <= 0:\n"," return []\n"," return [\" \".join(tokens[i:i+n]) for i in range(0, max(0, len(tokens) - n + 1))]\n","\n","# -----------------------------\n","# Extracción de señales / ruido\n","# -----------------------------\n","\n","def find_social_engineering_signals(text: str) -> Dict[str, List[str]]:\n"," lower = text.lower()\n"," out: Dict[str, List[str]] = {\"urgencia\": [], \"autoridad\": [], \"accion\": []}\n","\n"," for pat in URGENCIA:\n"," if re.search(pat, lower, flags=re.IGNORECASE):\n"," out[\"urgencia\"].append(pat)\n","\n"," for pat in AUTORIDAD:\n"," if re.search(pat, lower, flags=re.IGNORECASE):\n"," out[\"autoridad\"].append(pat)\n","\n"," for pat in ACCION_IMPERATIVA:\n"," if re.search(pat, lower, flags=re.IGNORECASE):\n"," out[\"accion\"].append(pat)\n","\n"," # Limpieza: no repetir patrones\n"," for k in out:\n"," out[k] = sorted(set(out[k]))\n"," return out\n","\n","def extract_technical_entities(text: str) -> Dict[str, List[str]]:\n"," return {\n"," \"urls\": sorted(set(RE_URL.findall(text))),\n"," \"emails\": sorted(set(RE_EMAIL.findall(text))),\n"," \"ips\": sorted(set(RE_IP.findall(text))),\n"," \"hashes\": sorted(set(RE_HASH.findall(text))),\n"," }\n","\n","def detect_noise_fragments(text: str) -> List[str]:\n"," fragments = []\n"," # Firma y disclaimer (si aplica)\n"," sig = RE_SIGNATURE.search(text)\n"," if sig:\n"," fragments.append(\"Posible firma/cierre: \" + sig.group(0)[:120] + (\"...\" if len(sig.group(0)) > 120 else \"\"))\n"," disc = RE_DISCLAIMER.search(text)\n"," if disc:\n"," fragments.append(\"Posible disclaimer: \" + disc.group(0)[:120] + (\"...\" if len(disc.group(0)) > 120 else \"\"))\n"," # HTML si hubiese\n"," if \"<\" in text and \">\" in text and RE_HTML_TAG.search(text):\n"," fragments.append(\"Contiene etiquetas HTML\")\n"," # Mayúsculas excesivas\n"," if sum(1 for c in text if c.isupper()) > 25:\n"," fragments.append(\"Uso alto de mayúsculas (posible presión/urgencia o ruido de formato)\")\n"," return fragments\n","\n","def anonymize(text: str) -> str:\n"," out = text\n"," for pattern, repl in ANON_MAP_PATTERNS:\n"," out = pattern.sub(repl, out)\n"," return out\n","\n","# -----------------------------\n","# Estructuras\n","# -----------------------------\n","\n","@dataclass\n","class Record:\n"," id: str\n"," fuente: str\n"," texto: str\n"," fecha: Optional[str] = None\n"," canal: Optional[str] = None\n"," etiqueta_preliminar: Optional[str] = None\n","\n","@dataclass\n","class AnalysisResult:\n"," id: str\n"," fuente: str\n"," ruido: List[str]\n"," senales: Dict[str, List[str]]\n"," entidades: Dict[str, List[str]]\n"," tokens: List[str]\n"," tokens_sin_stopwords: List[str]\n"," bigramas: List[str]\n"," trigramas: List[str]\n"," riesgos_sugeridos: List[str]\n","\n","# -----------------------------\n","# Lógica de riesgo (muy simple, formativa)\n","# -----------------------------\n","\n","def suggest_risks(fuente: str, signals: Dict[str, List[str]], entities: Dict[str, List[str]]) -> List[str]:\n"," risks = []\n"," if entities[\"urls\"] or entities[\"emails\"]:\n"," risks.append(\"Posible ingeniería social con enlaces/identidades (phishing)\")\n"," if signals[\"urgencia\"]:\n"," risks.append(\"Presión temporal (urgencia artificial)\")\n"," if signals[\"autoridad\"]:\n"," risks.append(\"Suplantación de autoridad institucional\")\n"," if signals[\"accion\"]:\n"," risks.append(\"Llamada a la acción (imperativos)\")\n","\n"," # Fix: Flatten the list of lists from entities.values() before joining\n"," all_entities_text = \" \".join([item for sublist in entities.values() for item in sublist])\n"," if fuente.lower() in (\"ticket\", \"itsm\", \"soporte\") and \"authentication failed\" in all_entities_text.lower():\n"," risks.append(\"Posible credencial comprometida o bloqueo\")\n"," return sorted(set(risks)) or [\"Riesgo no determinado (requiere contexto adicional)\"]\n","\n","# -----------------------------\n","# Carga de datos\n","# -----------------------------\n","\n","def load_records(path: str) -> List[Record]:\n"," ext = os.path.splitext(path)[1].lower()\n"," records: List[Record] = []\n","\n"," if ext == \".json\":\n"," with open(path, \"r\", encoding=\"utf-8\") as f:\n"," data = json.load(f)\n"," if isinstance(data, dict):\n"," data = data.get(\"records\", [])\n"," for row in data:\n"," records.append(Record(\n"," id=str(row.get(\"id\", \"\")),\n"," fuente=str(row.get(\"fuente\", \"\")),\n"," texto=str(row.get(\"texto\", \"\")),\n"," fecha=row.get(\"fecha\"),\n"," canal=row.get(\"canal\"),\n"," etiqueta_preliminar=row.get(\"etiqueta_preliminar\") or row.get(\"etiqueta\")\n"," ))\n"," elif ext == \".csv\":\n"," with open(path, \"r\", encoding=\"utf-8\") as f:\n"," reader = csv.DictReader(f)\n"," for row in reader:\n"," records.append(Record(\n"," id=str(row.get(\"id\", \"\")),\n"," fuente=str(row.get(\"fuente\", \"\")),\n"," texto=str(row.get(\"texto\", \"\")),\n"," fecha=row.get(\"fecha\"),\n"," canal=row.get(\"canal\"),\n"," etiqueta_preliminar=row.get(\"etiqueta_preliminar\") or row.get(\"etiqueta\")\n"," ))\n"," else:\n"," raise ValueError(\"Formato no soportado. Usa .csv o .json\")\n"," return records\n","\n","# -----------------------------\n","# Generación de análisis y salidas\n","# -----------------------------\n","\n","def analyze_records(records: List[Record]) -> List[AnalysisResult]:\n"," results: List[AnalysisResult] = []\n"," for r in records:\n"," raw = normalize_text(r.texto)\n"," noise = detect_noise_fragments(r.texto)\n"," signals = find_social_engineering_signals(raw)\n"," entities = extract_technical_entities(raw)\n","\n"," toks = tokenize(raw)\n"," toks_ns = remove_stopwords(toks)\n"," bi = ngrams([t.lower() for t in toks_ns], 2)[:20]\n"," tri = ngrams([t.lower() for t in toks_ns], 3)[:20]\n","\n"," risks = suggest_risks(r.fuente, signals, entities)\n","\n"," results.append(AnalysisResult(\n"," id=r.id,\n"," fuente=r.fuente,\n"," ruido=noise,\n"," senales=signals,\n"," entidades=entities,\n"," tokens=toks[:60],\n"," tokens_sin_stopwords=toks_ns[:60],\n"," bigramas=bi,\n"," trigramas=tri,\n"," riesgos_sugeridos=risks,\n"," ))\n"," return results\n","\n","def write_dataset_preliminar(records: List[Record], out_csv: str) -> None:\n"," # Dataset preliminar con texto anonimizado\n"," fieldnames = [\"id\", \"fuente\", \"fecha\", \"canal\", \"texto_anon\", \"etiqueta_preliminar\"]\n"," with open(out_csv, \"w\", encoding=\"utf-8\", newline=\"\") as f:\n"," writer = csv.DictWriter(f, fieldnames=fieldnames)\n"," writer.writeheader()\n"," for r in records:\n"," writer.writerow({\n"," \"id\": r.id,\n"," \"fuente\": r.fuente,\n"," \"fecha\": r.fecha or \"\",\n"," \"canal\": r.canal or \"\",\n"," \"texto_anon\": anonymize(normalize_text(r.texto)),\n"," \"etiqueta_preliminar\": r.etiqueta_preliminar or \"\"\n"," })\n","\n","def write_report_md(\n"," records: List[Record],\n"," analyses: List[AnalysisResult],\n"," out_md: str\n",") -> None:\n"," now = datetime.now().strftime(\"%Y-%m-%d %H:%M\")\n"," # Justificación de campos (Parte B)\n"," field_justification = [\n"," (\"id\", \"Identificador único para trazabilidad y control de versiones.\"),\n"," (\"fuente\", \"Permite diferenciar preprocesamiento y patrones por canal (correo/ticket/log/chat).\"),\n"," (\"fecha\", \"Apoya análisis temporal y correlación de eventos en incidentes.\"),\n"," (\"canal\", \"Indica el medio (email/ITSM/chat) para contexto operativo SOC/CSIRT.\"),\n"," (\"texto_anon\", \"Texto procesable respetando privacidad (anonimizado).\"),\n"," (\"etiqueta_preliminar\", \"Etiqueta exploratoria (Reto 1) y base para supervisado (Reto 2).\"),\n"," ]\n","\n"," with open(out_md, \"w\", encoding=\"utf-8\") as f:\n"," f.write(f\"# Actividad Integradora – Semana 1 (Reporte automatizado)\\n\\n\")\n"," f.write(f\"Generado: **{now}**\\n\\n\")\n"," f.write(\"## Parte A — Identificación y análisis textual\\n\\n\")\n","\n"," # Selecciona hasta 2 fuentes diferentes (como pide la actividad)\n"," # Si hay más, el reporte incluye todos pero marca dos primeras fuentes distintas.\n"," fuentes_vistas = []\n"," for a in analyses:\n"," if a.fuente not in fuentes_vistas:\n"," fuentes_vistas.append(a.fuente)\n"," if len(fuentes_vistas) == 2:\n"," break\n"," f.write(f\"Fuentes destacadas (primeras dos distintas): **{', '.join(fuentes_vistas) if fuentes_vistas else 'N/A'}**\\n\\n\")\n","\n"," for a in analyses:\n"," f.write(f\"### Registro {a.id} — Fuente: {a.fuente}\\n\\n\")\n"," f.write(\"**Ruido detectado (ejemplos):**\\n\")\n"," if a.ruido:\n"," for x in a.ruido[:3]:\n"," f.write(f\"- {x}\\n\")\n"," else:\n"," f.write(\"- (No se detectó ruido evidente con reglas básicas)\\n\")\n"," f.write(\"\\n**Señales lingüísticas (ingeniería social):**\\n\")\n"," f.write(f\"- Urgencia: {len(a.senales['urgencia'])} patrones\\n\")\n"," f.write(f\"- Autoridad: {len(a.senales['autoridad'])} patrones\\n\")\n"," f.write(f\"- Acción/imperativos: {len(a.senales['accion'])} patrones\\n\\n\")\n","\n"," f.write(\"**Entidades técnicas:**\\n\")\n"," f.write(f\"- URLs: {a.entidades['urls'][:3]}\\n\")\n"," f.write(f\"- Emails: {a.entidades['emails'][:3]}\\n\")\n"," f.write(f\"- IPs: {a.entidades['ips'][:3]}\\n\")\n"," f.write(f\"- Hashes: {a.entidades['hashes'][:3]}\\n\\n\")\n","\n"," f.write(\"**Tokens (muestra):**\\n\")\n"," f.write(f\"`{a.tokens}`\\n\\n\")\n"," f.write(\"**Tokens sin stopwords (muestra):**\\n\")\n"," f.write(f\"`{a.tokens_sin_stopwords}`\\n\\n\")\n"," f.write(\"**N-gramas (muestra):**\\n\")\n"," f.write(f\"- Bigramas: {a.bigramas[:10]}\\n\")\n"," f.write(f\"- Trigramas: {a.trigramas[:10]}\\n\\n\")\n","\n"," f.write(\"**Riesgos sugeridos:**\\n\")\n"," for r in a.riesgos_sugeridos:\n"," f.write(f\"- {r}\\n\")\n"," f.write(\"\\n---\\n\\n\")\n","\n"," f.write(\"## Parte B — Diseño preliminar del dataset\\n\\n\")\n"," f.write(\"### Estructura propuesta (campos mínimos)\\n\\n\")\n"," f.write(\"| Campo | Justificación |\\n|---|---|\\n\")\n"," for field, just in field_justification:\n"," f.write(f\"| `{field}` | {just} |\\n\")\n","\n"," f.write(\"\\n**Nota:** el archivo `dataset_preliminar.csv` se generó con el campo `texto_anon` (anonimización automática de URLs/emails/IPs/hashes).\\n\\n\")\n","\n"," f.write(\"## Parte C — Protocolo ético básico (resumen)\\n\\n\")\n"," f.write(\"- **Anonimización aplicada:** reemplazo de URLs, correos, IPs y hashes por placeholders.\\n\")\n"," f.write(\"- **Minimización:** se recomienda almacenar solo campos necesarios para el objetivo del análisis.\\n\")\n"," f.write(\"- **Restricciones de uso:** dataset solo para fines académicos; prohibida redistribución; acceso limitado al equipo docente/estudiantes del curso.\\n\\n\")\n","\n","def write_ethics_protocol_md(out_md: str) -> None:\n"," with open(out_md, \"w\", encoding=\"utf-8\") as f:\n"," f.write(\"# Protocolo ético básico para el corpus (Semana 1)\\n\\n\")\n"," f.write(\"## 1) Anonimización\\n\")\n"," f.write(\"- Reemplazar correos por `[EMAIL]`, URLs por `[URL]`, IPs por `[IP]`, hashes por `[HASH]`\\n\")\n"," f.write(\"- Eliminar nombres propios/cargos si aparecen (manual o con reglas adicionales).\\n\\n\")\n"," f.write(\"## 2) Minimización del dato\\n\")\n"," f.write(\"- Conservar solo campos indispensables para PLN (texto, fuente, fecha aproximada si aplica, etiqueta).\\n\")\n"," f.write(\"- Evitar almacenar credenciales, teléfonos, direcciones internas o información de infraestructura sensible.\\n\\n\")\n"," f.write(\"## 3) Seguridad y acceso\\n\")\n"," f.write(\"- Repositorio privado, acceso por roles.\\n\")\n"," f.write(\"- Control de versiones y bitácora de cambios.\\n\\n\")\n"," f.write(\"## 4) Restricciones de uso\\n\")\n"," f.write(\"- Uso exclusivo académico.\\n\")\n"," f.write(\"- Prohibida la publicación de datos crudos.\\n\")\n"," f.write(\"- El dataset debe destruirse o anonimizarse totalmente al cierre del curso, según lineamientos institucionales.\\n\")\n","\n","def run_pipeline(records: List[Record], out_dir: str = \"salidas_semana1\") -> None:\n"," os.makedirs(out_dir, exist_ok=True)\n","\n"," analyses = analyze_records(records)\n","\n"," out_dataset = os.path.join(out_dir, \"dataset_preliminar.csv\")\n"," out_report = os.path.join(out_dir, \"analisis_semana1.md\")\n"," out_ethics = os.path.join(out_dir, \"protocolo_etico.md\")\n","\n"," write_dataset_preliminar(records, out_dataset)\n"," write_report_md(records, analyses, out_report)\n"," write_ethics_protocol_md(out_ethics)\n","\n"," print(\"✅ Listo. Archivos generados:\")\n"," print(f\"- {out_dataset}\")\n"," print(f\"- {out_report}\")\n"," print(f\"- {out_ethics}\")\n","\n","# -----------------------------\n","# Ejecución de ejemplo\n","# -----------------------------\n","if __name__ == \"__main__\":\n"," # Ejemplo mínimo (puedes reemplazar por load_records(\"tu_archivo.csv\"))\n"," ejemplo = [\n"," Record(\n"," id=\"001\",\n"," fuente=\"correo\",\n"," fecha=\"2026-02-15\",\n"," canal=\"email\",\n"," texto=(\n"," \"Asunto: URGENTE: Verificación inmediata de su cuenta corporativa\\n\"\n"," \"Estimado usuario,\\n\"\n"," \"Hemos detectado actividad inusual en su cuenta.\\n\"\n"," \"Para evitar la suspensión inmediata, confirme sus credenciales aquí:\\n\"\n"," \"http://secure-verification-login.com\\n\"\n"," \"Atentamente,\\n\"\n"," \"Departamento de Seguridad TI\"\n"," ),\n"," etiqueta_preliminar=\"phishing\"\n"," ),\n"," Record(\n"," id=\"002\",\n"," fuente=\"ticket\",\n"," fecha=\"2026-02-14\",\n"," canal=\"ITSM\",\n"," texto=(\n"," \"Buen día, desde ayer no puedo conectarme a la VPN corporativa. \"\n"," \"Aparece el mensaje 'Authentication failed'. Mi usuario es jramirez. \"\n"," \"¿Podrían revisar si mi cuenta está bloqueada? Gracias.\"\n"," ),\n"," etiqueta_preliminar=\"legitimo\"\n"," ),\n"," ]\n","\n"," run_pipeline(ejemplo)"]},{"cell_type":"markdown","source":["### La siguiente pieza de código permite visualizar el analisis_semana1.md"],"metadata":{"id":"WoJ9Rkv3c0fp"}},{"cell_type":"code","metadata":{"id":"ccf19174"},"source":["with open('salidas_semana1/analisis_semana1.md', 'r', encoding='utf-8') as f:\n"," md_content = f.read()\n","\n","# Display the content in a markdown cell\n","from IPython.display import Markdown\n","display(Markdown(md_content))"],"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":["### La siguiente instrucción permite visualizar los campos del dataset_preliminar."],"metadata":{"id":"pxTGtnwYc_Da"}},{"cell_type":"code","metadata":{"id":"1ed8de6b"},"source":["print(df_dataset_preliminar.info())"],"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":["### El siguiente código permite visualizar un resumen estadístico de las columnas numéricas (si las hay) del dataset_preliminar"],"metadata":{"id":"SqXWBYFDdJFD"}},{"cell_type":"code","metadata":{"id":"aacaa9e5"},"source":["print('\\nAhora, un resumen estadístico de las columnas numéricas (si las hay):')\n","display(df_dataset_preliminar.describe(include='all'))"],"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":[],"metadata":{"id":"fGTLBklrdX9I"}},{"cell_type":"markdown","source":["### Para visualizar los primeros registros del dataset_preliminar"],"metadata":{"id":"94U6w4W-db37"}},{"cell_type":"code","metadata":{"id":"1499373f"},"source":["import pandas as pd\n","\n","df_dataset_preliminar = pd.read_csv('salidas_semana1/dataset_preliminar.csv')\n","display(df_dataset_preliminar)"],"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":["## Para usarlo con un dataset propio.\n","\n","Crea un CSV con columnas mínimas: id,fuente,texto (y opcionalmente fecha,canal,etiqueta), por ejemplo corpus_semana1.csv.\n","Reemplaza el bloque ejemplo = [...] por:\n","\n","\n","```\n","# Esto tiene formato de código\n","```\n","records = load_records(\"corpus_semana1.csv\")\n","run_pipeline(records)\n","\n"],"metadata":{"id":"RQT7Kb3Fdk_h"}}]}