diff --git a/docs/Material/Apuntes/apunte_backend.md b/docs/Material/Apuntes/apunte_backend.md new file mode 100644 index 00000000..f43551bd --- /dev/null +++ b/docs/Material/Apuntes/apunte_backend.md @@ -0,0 +1,684 @@ +--- +sidebar_position: 11 +--- + +# Backend con Node.js y Express + +Este apunte cubre cómo crear un servidor backend con Node.js y Express: desde el primer "Hola mundo" hasta una API completa con base de datos SQLite. + +## Introducción + +### ¿Qué es un backend? + +El **backend** es la parte de una aplicación que corre en el servidor. Se encarga de: +- Recibir peticiones HTTP del frontend (como vimos en el apunte de APIs REST). +- Procesar la lógica de negocio (validaciones, cálculos). +- Comunicarse con la base de datos (como vimos en el apunte de SQL). +- Devolver respuestas al frontend. + +``` + Navegador Servidor Base de Datos + (Frontend) (Backend) + ┌──────────┐ HTTP/JSON ┌──────────┐ SQL ┌──────────┐ + │ HTML │ ────────────> │ Node.js │ ───────────> │ SQLite │ + │ CSS │ <──────────── │ Express │ <─────────── │ │ + │ JS │ └──────────┘ └──────────┘ + └──────────┘ +``` + +--- + +## Node.js + +**Node.js** permite ejecutar JavaScript fuera del navegador. Mientras que en el apunte de JavaScript vimos JS corriendo en el navegador, Node.js lo ejecuta en el servidor. + +### npm y package.json + +**npm** (Node Package Manager) es el gestor de paquetes de Node.js. Permite instalar librerías de terceros. + +```bash +# Crear un proyecto nuevo +mkdir mi-api +cd mi-api +npm init -y +``` + +Esto crea un archivo `package.json` que describe tu proyecto y sus dependencias: + +```json +{ + "name": "mi-api", + "version": "1.0.0", + "main": "index.js", + "scripts": { + "start": "node index.js" + } +} +``` + +### Instalar dependencias + +```bash +npm install express +``` + +Esto descarga la librería en la carpeta `node_modules/` y la agrega a `package.json`. + +**Importante:** `node_modules/` nunca se sube al repositorio. Siempre incluila en el `.gitignore`: + +``` +node_modules/ +``` + +Cuando alguien clone tu proyecto, ejecuta `npm install` y npm descarga todas las dependencias automáticamente a partir del `package.json`. + +--- + +## Express + +**Express** es el framework más popular de Node.js para crear servidores HTTP. Simplifica enormemente el manejo de rutas, peticiones y respuestas. + +```bash +npm install express +``` + +--- + +## Primer servidor + +```javascript +// index.js +const express = require('express'); +const app = express(); + +app.get('/', (req, res) => { + res.json({ mensaje: 'Hola mundo desde Express!' }); +}); + +const PORT = 3000; +app.listen(PORT, () => { + console.log(`Servidor corriendo en http://localhost:${PORT}`); +}); +``` + +Ejecutalo: + +```bash +node index.js +``` + +Abrí `http://localhost:3000` en el navegador y vas a ver el JSON. También podés probarlo con curl (como vimos en el apunte de Bash): + +```bash +curl http://localhost:3000 +``` + +### ¿Qué hace cada parte? + +- `require('express')`: importa la librería Express. +- `express()`: crea una aplicación Express. +- `app.get('/', ...)`: define qué pasa cuando alguien hace un GET a `/`. +- `req`: el objeto con la información de la petición. +- `res`: el objeto para enviar la respuesta. +- `res.json(...)`: envía una respuesta en formato JSON. +- `app.listen(PORT, ...)`: inicia el servidor en el puerto indicado. + +--- + +## Rutas y métodos + +Las rutas definen qué hace tu servidor ante cada petición. Usando los métodos HTTP que vimos en el apunte de APIs REST: + +```javascript +// GET: obtener datos +app.get('/api/tareas', (req, res) => { + res.json([{ id: 1, titulo: 'Estudiar' }]); +}); + +// POST: crear un recurso +app.post('/api/tareas', (req, res) => { + const { titulo } = req.body; + res.status(201).json({ id: 2, titulo: titulo }); +}); + +// PUT: actualizar un recurso +app.put('/api/tareas/:id', (req, res) => { + const { id } = req.params; + const { titulo } = req.body; + res.json({ id: id, titulo: titulo }); +}); + +// DELETE: eliminar un recurso +app.delete('/api/tareas/:id', (req, res) => { + const { id } = req.params; + res.json({ mensaje: `Tarea ${id} eliminada` }); +}); +``` + +### Parámetros de ruta + +Los `:id` en la URL son **parámetros de ruta**. Se acceden con `req.params`: + +```javascript +app.get('/api/tareas/:id', (req, res) => { + const { id } = req.params; // si la URL es /api/tareas/5, id = "5" + res.json({ id: id }); +}); +``` + +### Parámetros de query + +Los parámetros de query (`?clave=valor`) se acceden con `req.query`: + +```javascript +// GET /api/tareas?completada=true&orden=fecha +app.get('/api/tareas', (req, res) => { + const { completada, orden } = req.query; + console.log(completada); // "true" + console.log(orden); // "fecha" +}); +``` + +### Body de la petición + +Para leer el body de peticiones POST/PUT, necesitás el middleware `express.json()`: + +```javascript +app.use(express.json()); // ANTES de las rutas + +app.post('/api/tareas', (req, res) => { + const { titulo, descripcion } = req.body; + // titulo y descripcion vienen del JSON que envió el cliente +}); +``` + +--- + +## Middleware + +Un **middleware** es una función que se ejecuta **entre** que llega la petición y se envía la respuesta. Se usa para tareas comunes como parsear JSON, loggear peticiones, manejar autenticación, etc. + +```javascript +// Middleware que loggea cada petición +function logger(req, res, next) { + console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`); + next(); // IMPORTANTE: llamar a next() para pasar al siguiente middleware/ruta +} + +app.use(express.json()); // parsea el body JSON +app.use(logger); // loggea cada petición +``` + +### Middlewares comunes de Express + +```javascript +app.use(express.json()); // parsear JSON en el body +app.use(express.static('public')); // servir archivos estáticos (HTML, CSS, JS) +``` + +`express.static('public')` sirve todos los archivos de la carpeta `public/` automáticamente. Si ponés un `index.html` ahí, se sirve cuando alguien accede a `/`. + +### El orden importa + +Los middlewares se ejecutan en el orden en que los registrás. Por eso, ponelos **antes** de las rutas: + +```javascript +// 1. Primero los middlewares +app.use(express.json()); +app.use(logger); + +// 2. Después las rutas +app.get('/api/tareas', (req, res) => { /* ... */ }); +``` + +--- + +## Estructura de un proyecto + +Cuando tu proyecto crece, organizá el código en carpetas: + +``` +mi-api/ +├── src/ +│ ├── routes/ +│ │ └── tareas.routes.js +│ ├── controllers/ +│ │ └── tareas.controller.js +│ ├── database/ +│ │ └── connection.js +│ └── middleware/ +│ └── logger.js +├── .env +├── .gitignore +├── index.js +└── package.json +``` + +### Separando las rutas + +```javascript +// src/routes/tareas.routes.js +const express = require('express'); +const router = express.Router(); +const tareasController = require('../controllers/tareas.controller'); + +router.get('/', tareasController.obtenerTodas); +router.get('/:id', tareasController.obtenerPorId); +router.post('/', tareasController.crear); +router.put('/:id', tareasController.actualizar); +router.delete('/:id', tareasController.eliminar); + +module.exports = router; +``` + +### Separando los controladores + +```javascript +// src/controllers/tareas.controller.js + +function obtenerTodas(req, res) { + res.json({ mensaje: 'Listado de tareas' }); +} + +function obtenerPorId(req, res) { + const { id } = req.params; + res.json({ mensaje: `Tarea ${id}` }); +} + +function crear(req, res) { + const { titulo } = req.body; + res.status(201).json({ mensaje: 'Tarea creada' }); +} + +function actualizar(req, res) { + const { id } = req.params; + res.json({ mensaje: `Tarea ${id} actualizada` }); +} + +function eliminar(req, res) { + const { id } = req.params; + res.json({ mensaje: `Tarea ${id} eliminada` }); +} + +module.exports = { obtenerTodas, obtenerPorId, crear, actualizar, eliminar }; +``` + +### Conectando todo en index.js + +```javascript +// index.js +const express = require('express'); +const app = express(); +const tareasRoutes = require('./src/routes/tareas.routes'); + +app.use(express.json()); +app.use('/api/tareas', tareasRoutes); + +const PORT = process.env.PORT || 3000; +app.listen(PORT, () => { + console.log(`Servidor corriendo en http://localhost:${PORT}`); +}); +``` + +Con `app.use('/api/tareas', tareasRoutes)` todas las rutas del router se montan bajo el prefijo `/api/tareas`. + +--- + +## Conectando a una base de datos SQL + +Como vimos en el apunte de SQL, las bases de datos permiten almacenar datos de forma estructurada. Vamos a usar **SQLite** — una base de datos liviana que guarda todo en un solo archivo, sin necesitar un servidor aparte. + +### Instalación + +```bash +npm install better-sqlite3 +``` + +### Creando la conexión + +```javascript +// src/database/connection.js +const Database = require('better-sqlite3'); +const path = require('path'); + +const dbPath = path.join(__dirname, 'tareas.db'); +const db = new Database(dbPath); + +db.pragma('journal_mode = WAL'); + +db.exec(` + CREATE TABLE IF NOT EXISTS tareas ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + titulo TEXT NOT NULL, + descripcion TEXT, + completada INTEGER DEFAULT 0, + fecha_creacion TEXT DEFAULT (datetime('now')) + ) +`); + +module.exports = db; +``` + +### Ejecutando consultas + +Con `better-sqlite3` usás **prepared statements**: + +```javascript +const db = require('./connection'); + +// SELECT todos +const tareas = db.prepare('SELECT * FROM tareas').all(); + +// SELECT uno por ID +const tarea = db.prepare('SELECT * FROM tareas WHERE id = ?').get(42); + +// INSERT +const resultado = db.prepare( + 'INSERT INTO tareas (titulo, descripcion) VALUES (?, ?)' +).run('Estudiar Node.js', 'Leer el apunte completo'); +console.log(resultado.lastInsertRowid); // el ID del nuevo registro + +// UPDATE +db.prepare( + 'UPDATE tareas SET completada = ? WHERE id = ?' +).run(1, 42); + +// DELETE +db.prepare('DELETE FROM tareas WHERE id = ?').run(42); +``` + +### SQL Injection: ¡cuidado! + +**Nunca concatenes** valores en el string SQL: + +```javascript +// PELIGROSO - vulnerable a SQL injection +const query = `SELECT * FROM tareas WHERE id = ${req.params.id}`; + +// SEGURO - consulta parametrizada +db.prepare('SELECT * FROM tareas WHERE id = ?').get(req.params.id); +``` + +Los placeholders `?` se reemplazan de forma segura, escapando caracteres especiales. + +--- + +## CRUD completo + +Actualizamos el controlador para usar la base de datos: + +```javascript +// src/controllers/tareas.controller.js +const db = require('../database/connection'); + +function obtenerTodas(req, res) { + try { + const tareas = db.prepare('SELECT * FROM tareas ORDER BY fecha_creacion DESC').all(); + res.json(tareas); + } catch (error) { + console.error('Error:', error.message); + res.status(500).json({ error: 'Error interno del servidor' }); + } +} + +function obtenerPorId(req, res) { + try { + const tarea = db.prepare('SELECT * FROM tareas WHERE id = ?').get(req.params.id); + if (!tarea) { + return res.status(404).json({ error: 'Tarea no encontrada' }); + } + res.json(tarea); + } catch (error) { + console.error('Error:', error.message); + res.status(500).json({ error: 'Error interno del servidor' }); + } +} + +function crear(req, res) { + try { + const { titulo, descripcion } = req.body; + + if (!titulo || titulo.trim() === '') { + return res.status(400).json({ error: 'El título es obligatorio' }); + } + + const resultado = db.prepare( + 'INSERT INTO tareas (titulo, descripcion) VALUES (?, ?)' + ).run(titulo.trim(), descripcion || null); + + const nuevaTarea = db.prepare('SELECT * FROM tareas WHERE id = ?').get( + resultado.lastInsertRowid + ); + res.status(201).json(nuevaTarea); + } catch (error) { + console.error('Error:', error.message); + res.status(500).json({ error: 'Error interno del servidor' }); + } +} + +function actualizar(req, res) { + try { + const { id } = req.params; + const { titulo, descripcion, completada } = req.body; + + const tareaExistente = db.prepare('SELECT * FROM tareas WHERE id = ?').get(id); + if (!tareaExistente) { + return res.status(404).json({ error: 'Tarea no encontrada' }); + } + + if (!titulo || titulo.trim() === '') { + return res.status(400).json({ error: 'El título es obligatorio' }); + } + + db.prepare( + 'UPDATE tareas SET titulo = ?, descripcion = ?, completada = ? WHERE id = ?' + ).run(titulo.trim(), descripcion || null, completada ? 1 : 0, id); + + const tareaActualizada = db.prepare('SELECT * FROM tareas WHERE id = ?').get(id); + res.json(tareaActualizada); + } catch (error) { + console.error('Error:', error.message); + res.status(500).json({ error: 'Error interno del servidor' }); + } +} + +function eliminar(req, res) { + try { + const { id } = req.params; + const tarea = db.prepare('SELECT * FROM tareas WHERE id = ?').get(id); + if (!tarea) { + return res.status(404).json({ error: 'Tarea no encontrada' }); + } + db.prepare('DELETE FROM tareas WHERE id = ?').run(id); + res.json({ mensaje: `Tarea ${id} eliminada correctamente` }); + } catch (error) { + console.error('Error:', error.message); + res.status(500).json({ error: 'Error interno del servidor' }); + } +} + +module.exports = { obtenerTodas, obtenerPorId, crear, actualizar, eliminar }; +``` + +### Probando con curl + +```bash +# Crear tareas +curl -X POST http://localhost:3000/api/tareas \ + -H "Content-Type: application/json" \ + -d '{"titulo": "Estudiar para el parcial", "descripcion": "Repasar SQL y Backend"}' + +# Listar todas +curl http://localhost:3000/api/tareas + +# Obtener una +curl http://localhost:3000/api/tareas/1 + +# Actualizar +curl -X PUT http://localhost:3000/api/tareas/1 \ + -H "Content-Type: application/json" \ + -d '{"titulo": "Estudiar para el parcial", "completada": true}' + +# Eliminar +curl -X DELETE http://localhost:3000/api/tareas/1 +``` + +--- + +## Manejo de errores + +### try/catch en las rutas + +Siempre envolvé las operaciones que pueden fallar en `try/catch`: + +```javascript +function obtenerTodas(req, res) { + try { + const tareas = db.prepare('SELECT * FROM tareas').all(); + res.json(tareas); + } catch (error) { + console.error('Error:', error.message); + res.status(500).json({ error: 'Error interno del servidor' }); + } +} +``` + +### Middleware de manejo de errores + +Express tiene un middleware especial con **4 parámetros** que se pone al final: + +```javascript +// Ruta 404 (después de todas las rutas) +app.use((req, res) => { + res.status(404).json({ error: 'Ruta no encontrada' }); +}); + +// Middleware de errores (siempre al final) +app.use((err, req, res, next) => { + console.error('Error:', err.message); + res.status(500).json({ error: 'Error interno del servidor' }); +}); +``` + +--- + +## Variables de entorno + +No hardcodees contraseñas, claves o configuraciones en el código. Usá **variables de entorno**. + +### Archivos .env + +```bash +npm install dotenv +``` + +Creá un archivo `.env`: + +``` +PORT=3000 +DB_PATH=./src/database/tareas.db +``` + +Leelo al inicio de tu app: + +```javascript +// index.js +require('dotenv').config(); + +const PORT = process.env.PORT || 3000; +``` + +**Importante**: `.env` nunca se sube al repositorio. Agregalo al `.gitignore`: + +``` +node_modules/ +.env +*.db +``` + +Creá un `.env.example` como referencia: + +``` +PORT=3000 +DB_PATH=./src/database/tareas.db +``` + +--- + +## Ejemplo integrador + +### Paso a paso para crear tu API + +```bash +# 1. Crear el proyecto +mkdir mi-api && cd mi-api +npm init -y +npm install express better-sqlite3 dotenv + +# 2. Crear la estructura +mkdir -p src/routes src/controllers src/database src/middleware +``` + +### index.js completo + +```javascript +require('dotenv').config(); +const express = require('express'); +const app = express(); +const tareasRoutes = require('./src/routes/tareas.routes'); + +app.use(express.json()); +app.use(express.static('public')); + +app.get('/', (req, res) => { + res.json({ + mensaje: 'API de Tareas funcionando', + endpoints: { + 'GET /api/tareas': 'Obtener todas las tareas', + 'GET /api/tareas/:id': 'Obtener una tarea por ID', + 'POST /api/tareas': 'Crear una nueva tarea', + 'PUT /api/tareas/:id': 'Actualizar una tarea', + 'DELETE /api/tareas/:id': 'Eliminar una tarea', + }, + }); +}); + +app.use('/api/tareas', tareasRoutes); + +app.use((req, res) => { + res.status(404).json({ error: 'Ruta no encontrada' }); +}); + +app.use((err, req, res, next) => { + console.error('Error:', err.message); + res.status(500).json({ error: 'Error interno del servidor' }); +}); + +const PORT = process.env.PORT || 3000; +app.listen(PORT, () => { + console.log(`Servidor corriendo en http://localhost:${PORT}`); +}); +``` + +### Iniciá el servidor + +```bash +npm start +``` + +--- + +## Resumen + +1. **Node.js** permite ejecutar JavaScript fuera del navegador. +2. **npm** gestiona las dependencias del proyecto. +3. **Express** simplifica la creación de servidores HTTP. +4. Definimos **rutas** para cada método HTTP (GET, POST, PUT, DELETE). +5. Los **middleware** procesan peticiones antes de que lleguen a las rutas. +6. **Organizamos** el proyecto en carpetas (routes, controllers, database). +7. **SQLite** es una base de datos simple, sin servidor aparte. +8. Las **consultas parametrizadas** previenen SQL injection. +9. El **manejo de errores** con try/catch mantiene la API robusta. +10. Las **variables de entorno** protegen información sensible. + +Con estos conocimientos, ya tenés las herramientas para construir APIs funcionales. En el apunte de Integración vamos a conectar este backend con un frontend y Docker Compose. diff --git a/docs/Material/Apuntes/apunte_integracion.md b/docs/Material/Apuntes/apunte_integracion.md new file mode 100644 index 00000000..7653167e --- /dev/null +++ b/docs/Material/Apuntes/apunte_integracion.md @@ -0,0 +1,740 @@ +--- +sidebar_position: 12 +--- + +# Integración: Frontend + Backend + Base de Datos + +Este es el apunte que une todo lo que vimos durante la cursada. Vas a aprender cómo conectar un frontend (HTML/CSS/JS), un backend (Express), una base de datos (SQLite), y orquestar todo con Docker Compose. + +## Introducción + +### La foto completa + +Una aplicación web full-stack tiene tres capas: + +``` + ┌─────────────────────────────────────────────────────────────┐ + │ USUARIO │ + │ (Navegador web) │ + └──────────────────────────┬──────────────────────────────────┘ + │ Interactúa + ┌──────────────────────────▼──────────────────────────────────┐ + │ FRONTEND │ + │ HTML + CSS + JavaScript │ + │ (lo que el usuario ve y toca) │ + └──────────────────────────┬──────────────────────────────────┘ + │ fetch() con HTTP/JSON + ┌──────────────────────────▼──────────────────────────────────┐ + │ BACKEND │ + │ Node.js + Express │ + │ (API REST, validación, lógica) │ + └──────────────────────────┬──────────────────────────────────┘ + │ SQL (consultas parametrizadas) + ┌──────────────────────────▼──────────────────────────────────┐ + │ BASE DE DATOS │ + │ SQLite │ + │ (almacenamiento persistente) │ + └─────────────────────────────────────────────────────────────┘ +``` + +Cada capa tiene su responsabilidad: +- **Frontend**: la interfaz de usuario. Se comunica con el backend usando `fetch()` (como vimos en el apunte de JavaScript). +- **Backend**: la API REST. Recibe peticiones, las valida, opera sobre la base de datos y devuelve respuestas (como vimos en los apuntes de APIs REST y Backend). +- **Base de datos**: almacena los datos de forma persistente (como vimos en el apunte de SQL). + +--- + +## Estructura del proyecto + +``` +mi-proyecto/ +├── frontend/ +│ ├── index.html +│ ├── styles.css +│ └── app.js +├── backend/ +│ ├── package.json +│ ├── server.js +│ ├── database.js +│ └── routes/ +│ └── tareas.js +├── docker-compose.yml +├── Dockerfile.frontend +├── Dockerfile.backend +├── nginx.conf +└── README.md +``` + +Separar frontend y backend en carpetas distintas mantiene todo organizado y facilita el deploy con Docker. + +--- + +## Conectando el Frontend al Backend + +La conexión se hace con `fetch()` desde JavaScript en el navegador. El frontend envía peticiones HTTP al backend y recibe respuestas JSON. + +### Ejemplo: cargar y mostrar tareas + +```javascript +// frontend/app.js +const API_URL = 'http://localhost:3000/api/tareas'; + +async function cargarTareas() { + try { + const respuesta = await fetch(API_URL); + + if (!respuesta.ok) { + throw new Error(`Error ${respuesta.status}`); + } + + const tareas = await respuesta.json(); + mostrarTareas(tareas); + } catch (error) { + if (error.message.includes('fetch')) { + mostrarError('No se puede conectar al servidor. ¿Está corriendo el backend?'); + } else { + mostrarError(error.message); + } + } +} +``` + +### Enviar datos al backend + +```javascript +async function agregarTarea(titulo) { + try { + const respuesta = await fetch(API_URL, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ titulo }), + }); + + if (!respuesta.ok) { + const errorData = await respuesta.json(); + throw new Error(errorData.error || 'Error al crear la tarea'); + } + + return await respuesta.json(); + } catch (error) { + mostrarError(error.message); + return null; + } +} +``` + +--- + +## CORS: qué es y por qué aparece + +Cuando el frontend corre en `http://localhost:8080` y el backend en `http://localhost:3000`, el navegador bloquea las peticiones porque son **orígenes distintos**. Esto se llama **CORS** (Cross-Origin Resource Sharing). + +### La solución + +Instalar el paquete `cors` en el backend: + +```bash +cd backend +npm install cors +``` + +```javascript +// backend/server.js +const cors = require('cors'); +app.use(cors()); // permite peticiones de cualquier origen +``` + +Sin esto, vas a ver en la consola del navegador un error como: + +``` +Access to fetch at 'http://localhost:3000/api/tareas' from origin +'http://localhost:8080' has been blocked by CORS policy +``` + +--- + +## Flujo completo de una operación + +Veamos paso a paso qué pasa cuando el usuario hace click en "Agregar Tarea": + +``` +1. USUARIO hace click en "Agregar" + │ +2. FRONTEND (app.js) + │ evento "submit" del formulario + │ lee el valor del input + │ llama a fetch() con POST + │ + ▼ +3. HTTP: POST /api/tareas + Body: { "titulo": "Estudiar para el parcial" } + │ + ▼ +4. BACKEND (Express) + │ recibe la petición + │ express.json() parsea el body + │ valida que el título no esté vacío + │ ejecuta INSERT en la base de datos + │ + ▼ +5. BASE DE DATOS (SQLite) + │ INSERT INTO tareas (titulo) VALUES ('Estudiar para el parcial') + │ devuelve el ID del nuevo registro + │ + ▼ +6. BACKEND + │ hace SELECT del registro recién creado + │ responde con 201 Created + el JSON de la tarea + │ + ▼ +7. HTTP: 201 Created + Body: { "id": 5, "titulo": "Estudiar para el parcial", "completada": 0 } + │ + ▼ +8. FRONTEND + │ recibe la respuesta + │ actualiza la lista de tareas en el DOM + │ + ▼ +9. USUARIO ve la nueva tarea en la página +``` + +--- + +## Docker Compose para todo el stack + +Como vimos en el apunte de Docker, Docker Compose permite levantar múltiples servicios con un solo comando. + +### Dockerfile.backend + +```dockerfile +FROM node:18-alpine +WORKDIR /app +COPY backend/package*.json ./ +RUN npm install +COPY backend/ . +RUN mkdir -p /app/datos +EXPOSE 3000 +CMD ["node", "server.js"] +``` + +### Dockerfile.frontend + +```dockerfile +FROM nginx:alpine +COPY frontend/ /usr/share/nginx/html/ +COPY nginx.conf /etc/nginx/conf.d/default.conf +EXPOSE 8080 +``` + +### nginx.conf + +```nginx +server { + listen 8080; + server_name localhost; + root /usr/share/nginx/html; + index index.html; + + location / { + try_files $uri $uri/ /index.html; + } +} +``` + +### docker-compose.yml + +```yaml +services: + frontend: + build: + context: . + dockerfile: Dockerfile.frontend + ports: + - "8080:8080" + depends_on: + - backend + + backend: + build: + context: . + dockerfile: Dockerfile.backend + ports: + - "3000:3000" + environment: + - PORT=3000 + - DB_PATH=/app/datos/datos.db + volumes: + - datos-db:/app/datos + +volumes: + datos-db: +``` + +Puntos clave: +- `depends_on`: el frontend espera a que el backend esté listo. +- `volumes`: los datos de la base de datos persisten entre reinicios. +- `environment`: variables de entorno para el backend. +- `ports`: mapeo de puertos del contenedor al host. + +### Comandos + +```bash +# Construir las imágenes +docker-compose build + +# Levantar todo +docker-compose up + +# Levantar en segundo plano +docker-compose up -d + +# Ver logs +docker-compose logs -f + +# Bajar todo +docker-compose down + +# Bajar todo + borrar volúmenes (borra la DB) +docker-compose down -v +``` + +--- + +## Manejo de errores end-to-end + +### En el Frontend + +```javascript +async function cargarTareas() { + try { + const respuesta = await fetch(API_URL); + + if (!respuesta.ok) { + const errorData = await respuesta.json(); + throw new Error(errorData.error || `Error ${respuesta.status}`); + } + + const tareas = await respuesta.json(); + mostrarTareas(tareas); + } catch (error) { + if (error.name === 'TypeError' && error.message === 'Failed to fetch') { + mostrarError('No se puede conectar al servidor.'); + } else { + mostrarError(error.message); + } + } +} +``` + +### En el Backend + +```javascript +router.post('/', (req, res) => { + try { + const { titulo } = req.body; + + if (!titulo || titulo.trim() === '') { + return res.status(400).json({ error: 'El título es obligatorio' }); + } + + const stmt = db.prepare('INSERT INTO tareas (titulo) VALUES (?)'); + const result = stmt.run(titulo.trim()); + + const nuevaTarea = db.prepare('SELECT * FROM tareas WHERE id = ?').get(result.lastInsertRowid); + res.status(201).json(nuevaTarea); + } catch (error) { + console.error('Error en POST /api/tareas:', error); + res.status(500).json({ error: 'Error interno del servidor' }); + } +}); +``` + +### Buenas prácticas + +1. **Siempre usá `try/catch`** en funciones async del frontend y handlers del backend. +2. **Nunca muestres detalles internos al usuario**. Los detalles van al `console.error`. +3. **Validá tanto en el frontend como en el backend**. La validación del frontend mejora la UX; la del backend protege los datos. + +--- + +## Ejemplo integrador completo: Lista de Tareas + +Este es un proyecto completo que podés copiar y correr. + +### frontend/index.html + +```html + + + + + + Lista de Tareas + + + +
+

Lista de Tareas

+ +
+ + +
+
+
+

Cargando tareas...

+
+
+ + + +``` + +### frontend/styles.css + +```css +* { margin: 0; padding: 0; box-sizing: border-box; } +body { font-family: 'Segoe UI', sans-serif; background: #f5f5f5; display: flex; justify-content: center; padding: 2rem; } +.container { width: 100%; max-width: 600px; } +h1 { text-align: center; margin-bottom: 1.5rem; color: #2c3e50; } +.form-tarea { display: flex; gap: 0.5rem; margin-bottom: 1rem; } +.form-tarea input { flex: 1; padding: 0.75rem; border: 2px solid #ddd; border-radius: 8px; font-size: 1rem; } +.form-tarea input:focus { outline: none; border-color: #3498db; } +.form-tarea button { padding: 0.75rem 1.5rem; background: #3498db; color: white; border: none; border-radius: 8px; font-size: 1rem; cursor: pointer; } +.form-tarea button:hover { background: #2980b9; } +.error { background: #fee; border: 1px solid #fcc; color: #c0392b; padding: 0.75rem; border-radius: 8px; margin-bottom: 1rem; } +.contador { text-align: right; font-size: 0.85rem; color: #888; margin-bottom: 0.5rem; } +.lista-tareas { display: flex; flex-direction: column; gap: 0.5rem; } +.tarea { display: flex; align-items: center; gap: 0.75rem; padding: 0.75rem 1rem; background: white; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); } +.tarea.completada span { text-decoration: line-through; color: #aaa; } +.tarea input[type="checkbox"] { width: 1.2rem; height: 1.2rem; cursor: pointer; } +.tarea span { flex: 1; } +.tarea button { padding: 0.4rem 0.8rem; background: #e74c3c; color: white; border: none; border-radius: 6px; cursor: pointer; } +.tarea button:hover { background: #c0392b; } +.sin-tareas { text-align: center; color: #aaa; padding: 2rem; font-style: italic; } +``` + +### frontend/app.js + +```javascript +const API_URL = 'http://localhost:3000/api/tareas'; + +async function cargarTareas() { + try { + const resp = await fetch(API_URL); + if (!resp.ok) throw new Error(`Error ${resp.status}`); + const tareas = await resp.json(); + mostrarTareas(tareas); + actualizarContador(tareas); + } catch (error) { + mostrarError('No se puede conectar al servidor.'); + } +} + +async function agregarTarea(titulo) { + const resp = await fetch(API_URL, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ titulo }), + }); + if (!resp.ok) { + const data = await resp.json(); + throw new Error(data.error); + } + return resp.json(); +} + +async function toggleTarea(id, completada) { + await fetch(`${API_URL}/${id}`, { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ completada }), + }); + cargarTareas(); +} + +async function eliminarTarea(id) { + await fetch(`${API_URL}/${id}`, { method: 'DELETE' }); + cargarTareas(); +} + +function mostrarTareas(tareas) { + const lista = document.getElementById('lista-tareas'); + lista.innerHTML = ''; + if (tareas.length === 0) { + lista.innerHTML = '

No hay tareas. ¡Agregá una!

'; + return; + } + tareas.forEach(tarea => { + const div = document.createElement('div'); + div.className = `tarea ${tarea.completada ? 'completada' : ''}`; + + const cb = document.createElement('input'); + cb.type = 'checkbox'; + cb.checked = tarea.completada === 1; + cb.addEventListener('change', () => toggleTarea(tarea.id, cb.checked)); + + const span = document.createElement('span'); + span.textContent = tarea.titulo; + + const btn = document.createElement('button'); + btn.textContent = 'Eliminar'; + btn.addEventListener('click', () => eliminarTarea(tarea.id)); + + div.appendChild(cb); + div.appendChild(span); + div.appendChild(btn); + lista.appendChild(div); + }); +} + +function actualizarContador(tareas) { + const completadas = tareas.filter(t => t.completada === 1).length; + document.getElementById('contador').textContent = `${completadas} de ${tareas.length} completadas`; +} + +function mostrarError(msg) { + const div = document.getElementById('error-mensaje'); + div.textContent = msg; + div.style.display = 'block'; + setTimeout(() => div.style.display = 'none', 5000); +} + +document.getElementById('form-tarea').addEventListener('submit', async (e) => { + e.preventDefault(); + const input = document.getElementById('input-titulo'); + const titulo = input.value.trim(); + if (!titulo) return; + try { + await agregarTarea(titulo); + input.value = ''; + cargarTareas(); + } catch (error) { + mostrarError(error.message); + } +}); + +cargarTareas(); +``` + +### backend/package.json + +```json +{ + "name": "lista-de-tareas-backend", + "version": "1.0.0", + "main": "server.js", + "scripts": { "start": "node server.js" }, + "dependencies": { + "better-sqlite3": "^11.0.0", + "cors": "^2.8.5", + "express": "^4.18.2" + } +} +``` + +### backend/server.js + +```javascript +const express = require('express'); +const cors = require('cors'); +const tareasRoutes = require('./routes/tareas'); + +const app = express(); +const PORT = process.env.PORT || 3000; + +app.use(cors()); +app.use(express.json()); +app.use('/api/tareas', tareasRoutes); + +app.get('/api/health', (req, res) => { + res.json({ status: 'ok', timestamp: new Date().toISOString() }); +}); + +app.use((req, res) => { + res.status(404).json({ error: 'Ruta no encontrada' }); +}); + +app.listen(PORT, () => { + console.log(`Servidor corriendo en http://localhost:${PORT}`); +}); +``` + +### backend/database.js + +```javascript +const Database = require('better-sqlite3'); +const path = require('path'); + +const dbPath = process.env.DB_PATH || path.join(__dirname, 'datos.db'); +const db = new Database(dbPath); + +db.pragma('journal_mode = WAL'); + +db.exec(` + CREATE TABLE IF NOT EXISTS tareas ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + titulo TEXT NOT NULL, + completada INTEGER DEFAULT 0, + creada_en TEXT DEFAULT (datetime('now')) + ) +`); + +module.exports = db; +``` + +### backend/routes/tareas.js + +```javascript +const express = require('express'); +const router = express.Router(); +const db = require('../database'); + +router.get('/', (req, res) => { + try { + const tareas = db.prepare('SELECT * FROM tareas ORDER BY creada_en DESC').all(); + res.json(tareas); + } catch (error) { + console.error('Error:', error); + res.status(500).json({ error: 'Error interno del servidor' }); + } +}); + +router.get('/:id', (req, res) => { + try { + const tarea = db.prepare('SELECT * FROM tareas WHERE id = ?').get(req.params.id); + if (!tarea) return res.status(404).json({ error: 'Tarea no encontrada' }); + res.json(tarea); + } catch (error) { + res.status(500).json({ error: 'Error interno del servidor' }); + } +}); + +router.post('/', (req, res) => { + try { + const { titulo } = req.body; + if (!titulo || titulo.trim() === '') { + return res.status(400).json({ error: 'El título es obligatorio' }); + } + const result = db.prepare('INSERT INTO tareas (titulo) VALUES (?)').run(titulo.trim()); + const nueva = db.prepare('SELECT * FROM tareas WHERE id = ?').get(result.lastInsertRowid); + res.status(201).json(nueva); + } catch (error) { + res.status(500).json({ error: 'Error interno del servidor' }); + } +}); + +router.put('/:id', (req, res) => { + try { + const tarea = db.prepare('SELECT * FROM tareas WHERE id = ?').get(req.params.id); + if (!tarea) return res.status(404).json({ error: 'Tarea no encontrada' }); + + const { titulo, completada } = req.body; + const nuevoTitulo = titulo !== undefined ? titulo.trim() : tarea.titulo; + const nuevaCompletada = completada !== undefined ? (completada ? 1 : 0) : tarea.completada; + + db.prepare('UPDATE tareas SET titulo = ?, completada = ? WHERE id = ?') + .run(nuevoTitulo, nuevaCompletada, req.params.id); + + const actualizada = db.prepare('SELECT * FROM tareas WHERE id = ?').get(req.params.id); + res.json(actualizada); + } catch (error) { + res.status(500).json({ error: 'Error interno del servidor' }); + } +}); + +router.delete('/:id', (req, res) => { + try { + const tarea = db.prepare('SELECT * FROM tareas WHERE id = ?').get(req.params.id); + if (!tarea) return res.status(404).json({ error: 'Tarea no encontrada' }); + db.prepare('DELETE FROM tareas WHERE id = ?').run(req.params.id); + res.json({ mensaje: 'Tarea eliminada correctamente' }); + } catch (error) { + res.status(500).json({ error: 'Error interno del servidor' }); + } +}); + +module.exports = router; +``` + +### Cómo correr el proyecto + +**Con Docker Compose** (recomendado): + +```bash +docker-compose build +docker-compose up +``` + +Abrí `http://localhost:8080` en el navegador. + +**Sin Docker** (para desarrollo): + +```bash +# Terminal 1: Backend +cd backend && npm install && node server.js + +# Terminal 2: Frontend +cd frontend && npx serve -l 8080 +``` + +--- + +## Tips para el TP + +### Git workflow + +1. **Usá branches**: nunca trabajes directamente en `main`. + ```bash + git checkout -b feature/agregar-tareas + ``` +2. **Commits chicos y descriptivos**: "Agrego formulario de tareas" > "Hago todo el TP". +3. **Usá Pull Requests** (como vimos en el apunte de Git). +4. **`.gitignore`**: + ``` + node_modules/ + *.db + .env + .DS_Store + ``` + +### Errores comunes + +| Error | Causa | Solución | +|-------|-------|----------| +| CORS bloqueado | Frontend y backend en distintos puertos | Instalar y usar el paquete `cors` | +| "Failed to fetch" | Backend no está corriendo | Verificar que `node server.js` esté ejecutándose | +| Formulario recarga la página | Falta `event.preventDefault()` | Agregarlo en el handler del submit | +| Datos se pierden al reiniciar Docker | No hay volumen configurado | Agregar volumen en `docker-compose.yml` | +| `node_modules` en el repo | Falta en `.gitignore` | Agregar `node_modules/` al `.gitignore` | +| No funciona con Docker | Solo probaste sin Docker | Siempre probar con `docker-compose build && docker-compose up` | + +### Cómo debuggear + +- **Consola del navegador (F12)**: errores de JavaScript y peticiones HTTP. +- **Tab "Network" en DevTools**: ver cada petición, su status, headers y body. +- **Logs del backend**: `console.log()` en Express → se ven en la terminal o en `docker-compose logs`. +- **curl**: probar endpoints sin depender del frontend. + +--- + +## Resumen + +| Capa | Tecnología | Responsabilidad | +|------|-----------|----------------| +| Frontend | HTML + CSS + JavaScript | Interfaz de usuario, `fetch()` al backend | +| Backend | Node.js + Express | API REST, validación, lógica | +| Base de datos | SQLite | Almacenamiento persistente | +| Orquestación | Docker Compose | Levantar todo con un solo comando | + +El flujo siempre es: **Usuario → Frontend → Backend → Base de Datos** y viceversa. + +Si te trabás, revisá los apuntes individuales: +- [HTML y CSS](./apunte_html_cs) +- [JavaScript](./apunte_javascript) +- [APIs REST](./apunte_rest_api) +- [Backend](./apunte_backend) +- [SQL](./apunte_sql) +- [Docker](./docker) +- [Git](./git) diff --git a/docs/Material/Apuntes/apunte_javascript.md b/docs/Material/Apuntes/apunte_javascript.md new file mode 100644 index 00000000..364f97a2 --- /dev/null +++ b/docs/Material/Apuntes/apunte_javascript.md @@ -0,0 +1,813 @@ +--- +sidebar_position: 9 +--- + +# JavaScript en el Navegador + +Este apunte cubre JavaScript aplicado al navegador: cómo manipular páginas web, responder a acciones del usuario y comunicarse con servidores externos. + +## Introducción a JavaScript + +**JavaScript** es un lenguaje de programación que originalmente fue creado para darle interactividad a las páginas web. Mientras que HTML define la estructura y CSS el estilo (como vimos en el apunte de HTML y CSS), JavaScript agrega el **comportamiento**: qué pasa cuando hacés click en un botón, cómo se valida un formulario, cómo se cargan datos sin recargar la página. + +### Dónde se ejecuta + +JavaScript se ejecuta dentro del **navegador** (Chrome, Firefox, etc.). Cada navegador tiene un motor de JavaScript que interpreta y ejecuta el código. Cuando abrís una página web que tiene JavaScript, el navegador lo descarga y lo ejecuta automáticamente. + +### Cómo incluir JavaScript en HTML + +Hay dos formas principales: + +**1. Script interno** (dentro del HTML): + +```html + + + + + Mi página + + +

Hola mundo

+ + + + +``` + +**2. Script externo** (archivo separado, recomendado): + +```html + + +``` + +```javascript +// En app.js +console.log("Hola desde un archivo externo"); +``` + +Siempre es mejor usar archivos externos porque mantiene el código organizado. Poné el ` + + +``` + +### El JavaScript (app.js) + +```javascript +const inputBusqueda = document.querySelector("#inputBusqueda"); +const btnBuscar = document.querySelector("#btnBuscar"); +const resultadosDiv = document.querySelector("#resultados"); +const mensajeP = document.querySelector("#mensaje"); +const favoritosDiv = document.querySelector("#favoritos"); + +function obtenerFavoritos() { + return JSON.parse(localStorage.getItem("paisFavoritos")) || []; +} + +function guardarFavoritos(favoritos) { + localStorage.setItem("paisFavoritos", JSON.stringify(favoritos)); +} + +function esFavorito(nombrePais) { + return obtenerFavoritos().some(fav => fav.nombre === nombrePais); +} + +function toggleFavorito(pais) { + let favoritos = obtenerFavoritos(); + const nombrePais = pais.name.common; + + if (esFavorito(nombrePais)) { + favoritos = favoritos.filter(fav => fav.nombre !== nombrePais); + } else { + favoritos.push({ + nombre: nombrePais, + capital: pais.capital ? pais.capital[0] : "No disponible", + poblacion: pais.population, + bandera: pais.flags.png + }); + } + + guardarFavoritos(favoritos); + mostrarFavoritos(); + buscarPaises(inputBusqueda.value.trim()); +} + +async function buscarPaises(termino) { + if (!termino) return; + mensajeP.textContent = "Buscando..."; + resultadosDiv.innerHTML = ""; + + try { + const response = await fetch( + `https://restcountries.com/v3.1/name/${encodeURIComponent(termino)}` + ); + + if (!response.ok) { + mensajeP.textContent = "No se encontraron países con ese nombre."; + return; + } + + const paises = await response.json(); + mensajeP.textContent = `Se encontraron ${paises.length} resultado(s).`; + + paises.forEach(pais => { + const card = document.createElement("div"); + card.classList.add("pais-card"); + + card.innerHTML = ` + Bandera de ${pais.name.common} +

${pais.name.common}

+

Capital: ${pais.capital ? pais.capital[0] : "No disponible"}

+

Población: ${pais.population.toLocaleString("es-AR")}

+ `; + + const btnFav = document.createElement("button"); + btnFav.classList.add("btn-favorito"); + btnFav.classList.add(esFavorito(pais.name.common) ? "quitar" : "agregar"); + btnFav.textContent = esFavorito(pais.name.common) ? "Quitar de favoritos" : "Agregar a favoritos"; + btnFav.addEventListener("click", () => toggleFavorito(pais)); + + card.appendChild(btnFav); + resultadosDiv.appendChild(card); + }); + } catch (error) { + mensajeP.textContent = "Hubo un error al buscar."; + console.error(error); + } +} + +function mostrarFavoritos() { + const favoritos = obtenerFavoritos(); + favoritosDiv.innerHTML = ""; + + if (favoritos.length === 0) { + favoritosDiv.innerHTML = "

No tenés países favoritos todavía.

"; + return; + } + + favoritos.forEach(fav => { + const card = document.createElement("div"); + card.classList.add("pais-card"); + card.innerHTML = ` + Bandera de ${fav.nombre} +

${fav.nombre}

+

Capital: ${fav.capital}

+

Población: ${fav.poblacion.toLocaleString("es-AR")}

+ `; + + const btnQuitar = document.createElement("button"); + btnQuitar.textContent = "Quitar de favoritos"; + btnQuitar.classList.add("btn-favorito", "quitar"); + btnQuitar.addEventListener("click", () => { + guardarFavoritos(obtenerFavoritos().filter(f => f.nombre !== fav.nombre)); + mostrarFavoritos(); + }); + + card.appendChild(btnQuitar); + favoritosDiv.appendChild(card); + }); +} + +btnBuscar.addEventListener("click", () => buscarPaises(inputBusqueda.value.trim())); +inputBusqueda.addEventListener("keydown", (e) => { + if (e.key === "Enter") buscarPaises(inputBusqueda.value.trim()); +}); + +mostrarFavoritos(); +``` + +### ¿Qué cubre este ejemplo? + +- **Variables y tipos de datos**: `const`, `let`, strings, arrays, objetos. +- **Funciones**: declaradas, arrow functions, funciones asíncronas. +- **DOM**: `querySelector`, `createElement`, `appendChild`, `classList`, `textContent`, `innerHTML`. +- **Eventos**: `addEventListener` para `click`, `keydown`. +- **Formularios**: lectura de `.value`, validación básica. +- **Fetch API**: `async/await`, manejo de errores con `try/catch`, parseo de JSON. +- **LocalStorage**: `getItem`, `setItem`, `JSON.stringify`, `JSON.parse`. + +Te recomendamos que copies este código, lo pruebes en tu navegador, y después lo modifiques. La mejor forma de aprender JavaScript es escribiendo código y experimentando. diff --git a/docs/Material/Apuntes/apunte_rest_api.md b/docs/Material/Apuntes/apunte_rest_api.md new file mode 100644 index 00000000..54b3e777 --- /dev/null +++ b/docs/Material/Apuntes/apunte_rest_api.md @@ -0,0 +1,475 @@ +--- +sidebar_position: 10 +--- + +# APIs REST + +Este apunte cubre los conceptos fundamentales para entender y diseñar APIs REST: el protocolo HTTP, los métodos, los códigos de estado, JSON y los principios de diseño RESTful. + +## Introducción + +### ¿Qué es una API? + +Una **API** (Application Programming Interface) es un contrato de comunicación entre dos programas. Define cómo un programa puede pedirle cosas a otro y qué formato van a tener las respuestas. + +Una analogía: pensá en un restaurante. Vos (el cliente) no vas directo a la cocina a prepararte la comida. Le pedís al mozo (la API) lo que querés, el mozo le lleva tu pedido a la cocina (el servidor), y después te trae la respuesta (los datos). + +En el desarrollo web, las APIs permiten que el **frontend** (lo que ve el usuario en el navegador) se comunique con el **backend** (el servidor que procesa datos y los guarda en la base de datos). + +``` + Navegador (Frontend) Servidor (Backend) Base de Datos + ┌──────────────────┐ ┌──────────────────┐ ┌──────────────┐ + │ HTML + CSS + JS │ ──API──>│ Procesa pedidos │──SQL─>│ Guarda datos│ + │ │ <──API──│ Devuelve datos │<─SQL──│ │ + └──────────────────┘ └──────────────────┘ └──────────────┘ +``` + +--- + +## El protocolo HTTP + +### ¿Cómo funciona la web? + +La web funciona con un modelo **cliente-servidor**: + +1. El **cliente** (tu navegador) envía una **petición** (request) al servidor. +2. El **servidor** procesa la petición y devuelve una **respuesta** (response). + +Esa comunicación se hace a través del protocolo **HTTP** (HyperText Transfer Protocol). Cada vez que visitás una página web, tu navegador está haciendo peticiones HTTP. + +--- + +## Anatomía de una petición HTTP + +Una petición HTTP tiene estas partes: + +```http +POST /api/canciones HTTP/1.1 +Host: mi-api.com +Content-Type: application/json +Authorization: Bearer token123 + +{ + "titulo": "De música ligera", + "artista": "Soda Stereo" +} +``` + +| Parte | Descripción | +|-------|-------------| +| **Método** | `POST` — la acción que querés hacer | +| **URL** | `/api/canciones` — el recurso sobre el que operás | +| **Headers** | Metadatos (tipo de contenido, autenticación, etc.) | +| **Body** | Los datos que enviás (no todas las peticiones lo tienen) | + +Y una respuesta: + +```http +HTTP/1.1 201 Created +Content-Type: application/json + +{ + "id": 5, + "titulo": "De música ligera", + "artista": "Soda Stereo" +} +``` + +--- + +## Métodos HTTP + +Los métodos indican **qué querés hacer** con un recurso. + +| Método | Acción | Analogía | ¿Tiene body? | +|--------|--------|----------|--------------| +| `GET` | Obtener datos | "Dame la carta del menú" | No | +| `POST` | Crear un recurso nuevo | "Quiero pedir este plato" | Sí | +| `PUT` | Reemplazar un recurso completo | "Cambiame todo el pedido" | Sí | +| `PATCH` | Modificar parcialmente | "Agregale papas al pedido" | Sí | +| `DELETE` | Eliminar un recurso | "Cancelá mi pedido" | No | + +### GET: Obtener datos + +```http +GET /api/canciones HTTP/1.1 +Host: mi-api.com +``` + +Respuesta: +```json +[ + { "id": 1, "titulo": "De música ligera", "artista": "Soda Stereo" }, + { "id": 2, "titulo": "Muchacha ojos de papel", "artista": "Almendra" } +] +``` + +### POST: Crear un recurso + +```http +POST /api/canciones HTTP/1.1 +Content-Type: application/json + +{ + "titulo": "Rezo por vos", + "artista": "Charly García" +} +``` + +Respuesta (`201 Created`): +```json +{ + "id": 3, + "titulo": "Rezo por vos", + "artista": "Charly García" +} +``` + +### PUT: Reemplazar un recurso + +```http +PUT /api/canciones/3 HTTP/1.1 +Content-Type: application/json + +{ + "titulo": "Rezo por vos", + "artista": "Charly García", + "album": "Clics Modernos" +} +``` + +### DELETE: Eliminar un recurso + +```http +DELETE /api/canciones/3 HTTP/1.1 +``` + +Respuesta: `204 No Content` + +--- + +## Códigos de estado HTTP + +Los códigos de estado indican **qué pasó** con tu petición: + +``` + 2xx → Éxito + 3xx → Redirección + 4xx → Error del cliente (tu culpa) + 5xx → Error del servidor (culpa del servidor) +``` + +### 2xx: Todo salió bien + +| Código | Nombre | ¿Cuándo se usa? | +|--------|--------|-----------------| +| `200` | OK | Petición exitosa (GET, PUT, PATCH) | +| `201` | Created | Se creó un recurso nuevo (POST) | +| `204` | No Content | Éxito, sin body en la respuesta (DELETE) | + +### 4xx: Error del cliente + +| Código | Nombre | ¿Cuándo se usa? | +|--------|--------|-----------------| +| `400` | Bad Request | JSON inválido, faltan campos, etc. | +| `401` | Unauthorized | No te autenticaste | +| `403` | Forbidden | Te autenticaste pero no tenés permiso | +| `404` | Not Found | El recurso no existe | + +La diferencia entre `401` y `403`: +- **401**: "No sé quién sos" (falta autenticación). +- **403**: "Sé quién sos, pero no podés hacer esto" (falta autorización). + +### 5xx: Error del servidor + +| Código | Nombre | ¿Cuándo se usa? | +|--------|--------|-----------------| +| `500` | Internal Server Error | Algo explotó en el servidor | +| `502` | Bad Gateway | El servidor intermedio recibió una respuesta inválida | +| `503` | Service Unavailable | Servidor temporalmente fuera de servicio | + +Tip: si empieza con **4**, el problema es **tuyo**. Si empieza con **5**, el problema es del **servidor**. + +--- + +## JSON + +### ¿Qué es JSON? + +**JSON** (JavaScript Object Notation) es el formato más usado para intercambiar datos entre cliente y servidor. Aunque tiene "JavaScript" en el nombre, es independiente del lenguaje. + +### Reglas de sintaxis + +1. Las **claves** siempre van entre comillas dobles. +2. Los **strings** siempre van entre comillas dobles (no simples). +3. No se permiten **comas finales** (trailing commas). +4. No se permiten **comentarios**. + +### Tipos de datos en JSON + +| Tipo | Ejemplo | +|------|---------| +| String | `"Hola mundo"` | +| Number | `42`, `3.14` | +| Boolean | `true`, `false` | +| Null | `null` | +| Object | `{ "clave": "valor" }` | +| Array | `[1, 2, 3]` | + +### Ejemplo + +```json +{ + "id": 1, + "nombre": "Martín López", + "padron": 108432, + "regular": true, + "promedio": 7.85, + "materias": [ + { "nombre": "Algoritmos", "nota": 8 }, + { "nombre": "Intro al Desarrollo", "nota": 9 } + ] +} +``` + +### JSON vs. objetos de JavaScript + +| Característica | JSON | Objeto JavaScript | +|----------------|------|-------------------| +| Claves | Siempre con comillas dobles | Pueden ir sin comillas | +| Strings | Solo comillas dobles | Comillas simples o dobles | +| Trailing commas | No permitidas | Permitidas | +| Funciones como valor | No | Sí | + +--- + +## ¿Qué es REST? + +**REST** (Representational State Transfer) es un **estilo de arquitectura** para diseñar APIs. No es un protocolo ni una librería: es un conjunto de principios. Una API que sigue estos principios se llama **API RESTful**. + +### Principios de REST + +**1. Todo es un recurso.** Los datos se modelan como recursos, cada uno con una URI única: + +``` +/usuarios → la colección de todos los usuarios +/usuarios/42 → el usuario con id 42 +/canciones/5 → la canción con id 5 +``` + +**2. Se usan los métodos HTTP como verbos.** En lugar de `/obtenerUsuario` o `/borrarCancion`, REST usa: + +``` +GET /usuarios/42 → obtener el usuario 42 +POST /usuarios → crear un usuario nuevo +PUT /usuarios/42 → actualizar el usuario 42 +DELETE /usuarios/42 → eliminar el usuario 42 +``` + +**3. Sin estado (Stateless).** Cada petición contiene toda la información necesaria. El servidor no "recuerda" peticiones anteriores. + +**4. Interfaz uniforme.** Todos los recursos se manipulan de la misma manera. Si sabés cómo funciona `/usuarios`, ya sabés cómo funciona `/canciones`. + +--- + +## Diseño de endpoints + +### Reglas de diseño + +1. **Usá sustantivos, no verbos:** + - `GET /canciones` (bien) + - `GET /obtenerCanciones` (mal) + +2. **Usá plural:** `/canciones`, no `/cancion`. + +3. **Usá minúsculas y guiones:** `/listas-de-reproduccion`, no `/listasDeReproduccion`. + +4. **Jerarquía con barras:** + ``` + /artistas/5/albums → los álbumes del artista 5 + /artistas/5/albums/3 → el álbum 3 del artista 5 + ``` + +5. **Parámetros de query para filtrar:** + ``` + /canciones?genero=rock + /canciones?orden=anio&dir=desc + /canciones?pagina=2&limite=20 + ``` + +### CRUD mapeado a HTTP + +Si ya viste SQL (como vimos en el apunte de SQL), te va a resultar familiar: + +| Operación CRUD | Método HTTP | Endpoint | SQL equivalente | +|----------------|-------------|----------|-----------------| +| **C**reate | `POST` | `POST /canciones` | `INSERT INTO canciones` | +| **R**ead | `GET` | `GET /canciones/5` | `SELECT * FROM canciones WHERE id = 5` | +| **U**pdate | `PUT/PATCH` | `PUT /canciones/5` | `UPDATE canciones SET ... WHERE id = 5` | +| **D**elete | `DELETE` | `DELETE /canciones/5` | `DELETE FROM canciones WHERE id = 5` | + +--- + +## Headers importantes + +### Content-Type + +Indica el formato del body: + +```http +Content-Type: application/json +``` + +### Accept + +Indica en qué formato querés la respuesta: + +```http +Accept: application/json +``` + +### Authorization + +Para autenticarte ante la API: + +```http +Authorization: Bearer eyJhbGciOiJIUzI1NiIs... +``` + +--- + +## Probando APIs + +### Usando curl desde la terminal + +Como vimos en el apunte de Bash, `curl` permite hacer peticiones HTTP desde la terminal. + +**GET:** +```bash +curl https://jsonplaceholder.typicode.com/todos/1 +``` + +**POST:** +```bash +curl -X POST https://jsonplaceholder.typicode.com/todos \ + -H "Content-Type: application/json" \ + -d '{ + "userId": 1, + "title": "Estudiar APIs REST", + "completed": false + }' +``` + +**PUT:** +```bash +curl -X PUT https://jsonplaceholder.typicode.com/todos/1 \ + -H "Content-Type: application/json" \ + -d '{"userId": 1, "title": "Estudiar APIs REST", "completed": true}' +``` + +**DELETE:** +```bash +curl -X DELETE https://jsonplaceholder.typicode.com/todos/1 +``` + +| Flag | Descripción | +|------|-------------| +| `-X POST` | Método HTTP | +| `-H` | Agregar un header | +| `-d` | Datos del body | +| `-v` | Modo verbose (ver todos los detalles) | + +### Postman y Thunder Client + +Para probar APIs con interfaz gráfica: +- **Postman**: aplicación de escritorio, muy popular. +- **Thunder Client**: extensión de VS Code (como vimos en el apunte de Editores de Texto, si usás VS Code es muy conveniente). + +--- + +## Ejemplo completo: API de lista de tareas + +### El recurso: Tarea + +```json +{ + "id": 1, + "titulo": "Estudiar para el parcial de Intro", + "descripcion": "Repasar APIs REST y SQL", + "completada": false, + "prioridad": "alta" +} +``` + +### Endpoints + +| Método | Endpoint | Descripción | Código de éxito | +|--------|----------|-------------|-----------------| +| `GET` | `/api/tareas` | Listar todas | `200 OK` | +| `GET` | `/api/tareas/:id` | Obtener una por ID | `200 OK` | +| `POST` | `/api/tareas` | Crear una nueva | `201 Created` | +| `PATCH` | `/api/tareas/:id` | Actualizar una | `200 OK` | +| `DELETE` | `/api/tareas/:id` | Eliminar una | `204 No Content` | + +### Ejemplo de cada operación con curl + +**Listar:** +```bash +curl http://localhost:3000/api/tareas +``` + +**Crear:** +```bash +curl -X POST http://localhost:3000/api/tareas \ + -H "Content-Type: application/json" \ + -d '{"titulo": "Leer apunte de Docker", "prioridad": "baja"}' +``` + +**Actualizar:** +```bash +curl -X PATCH http://localhost:3000/api/tareas/1 \ + -H "Content-Type: application/json" \ + -d '{"completada": true}' +``` + +**Eliminar:** +```bash +curl -X DELETE http://localhost:3000/api/tareas/1 +``` + +### Flujo visual + +``` + CLIENTE SERVIDOR + │ │ + │ POST /api/tareas │ + │ { "titulo": "Estudiar" } │ + │ ─────────────────────────────> │ + │ │ Guarda en la base de datos + │ 201 Created │ + │ { "id": 3, "titulo": ... } │ + │ <───────────────────────────── │ + │ │ + │ GET /api/tareas │ + │ ─────────────────────────────> │ + │ │ Consulta la base de datos + │ 200 OK │ + │ [{ "id": 1, ... }, ...] │ + │ <───────────────────────────── │ +``` + +--- + +## Resumen + +| Concepto | Idea clave | +|----------|-----------| +| **API** | Contrato de comunicación entre dos programas | +| **HTTP** | Protocolo que usan clientes y servidores | +| **Métodos HTTP** | GET (leer), POST (crear), PUT/PATCH (actualizar), DELETE (borrar) | +| **Códigos de estado** | 2xx = éxito, 4xx = error del cliente, 5xx = error del servidor | +| **JSON** | Formato estándar para enviar y recibir datos | +| **REST** | Principios de diseño: recursos, URIs, métodos HTTP, sin estado | +| **Endpoints** | URLs que representan recursos, usando sustantivos en plural | + +En el apunte de Backend vamos a poner todo esto en práctica, creando un servidor que exponga una API REST real y funcional. diff --git a/docs/Material/Apuntes/linux_terminal.md b/docs/Material/Apuntes/linux_terminal.md new file mode 100644 index 00000000..d96a6a89 --- /dev/null +++ b/docs/Material/Apuntes/linux_terminal.md @@ -0,0 +1,605 @@ +--- +sidebar_label: "Linux y Terminal" +--- + +# Linux y Terminal + +## ¿Que es Linux? + +Linux es un **sistema operativo de codigo abierto** basado en Unix, creado por Linus Torvalds en 1991. A diferencia de Windows o macOS, Linux es **libre y gratuito**, lo que significa que cualquier persona puede usarlo, modificarlo y distribuirlo. + +### ¿Por que es importante para los desarrolladores? + +- **Servidores**: La gran mayoria de los servidores en el mundo corren Linux. Si vas a hacer deploy de una aplicacion, muy probablemente sea en un servidor Linux. +- **Herramientas de desarrollo**: Muchas herramientas de desarrollo (Git, Docker, Node.js, etc.) fueron diseñadas originalmente para Linux. +- **Estabilidad y seguridad**: Linux es conocido por ser un sistema operativo muy estable y seguro. +- **Codigo abierto**: Podes ver, modificar y aprender del codigo fuente del sistema operativo. + +### Distribuciones + +Linux no viene en una sola version. Existen muchas **distribuciones** (o "distros"), que son versiones de Linux empaquetadas con diferentes programas y configuraciones: + +| Distribucion | Descripcion | +|---|---| +| **Ubuntu** | La mas popular para principiantes. Basada en Debian. | +| **Debian** | Muy estable, usada en servidores. Base de muchas otras distros. | +| **Fedora** | Enfocada en las ultimas tecnologias. Patrocinada por Red Hat. | +| **Arch Linux** | Para usuarios avanzados. Altamente personalizable. | +| **Linux Mint** | Similar a Ubuntu pero con una interfaz mas parecida a Windows. | + +Todas las distribuciones comparten el **kernel** (nucleo) de Linux, que es la parte fundamental que se comunica con el hardware. + +--- + +## La Terminal + +### ¿Que es una terminal? + +La **terminal** (tambien llamada consola o linea de comandos) es una interfaz de texto que te permite comunicarte con el sistema operativo escribiendo comandos. Cuando abris una terminal, lo que se ejecuta adentro es un **shell** (como Bash, Zsh o Fish), que es el programa que interpreta los comandos que escribis. + +### ¿Por que usar la terminal? + +- **Velocidad**: Muchas tareas se realizan mas rapido escribiendo un comando que navegando por menus graficos. +- **Automatizacion**: Podes combinar comandos y crear scripts para automatizar tareas repetitivas. +- **Acceso remoto**: Cuando trabajas con servidores, la terminal es generalmente la unica forma de interactuar con ellos (via SSH). +- **Control total**: La terminal te da acceso a funcionalidades que no estan disponibles desde la interfaz grafica. + +### Terminal vs Interfaz Grafica (GUI) + +| Terminal (CLI) | Interfaz Grafica (GUI) | +|---|---| +| Se interactua escribiendo comandos | Se interactua con el mouse y botones | +| Requiere conocer los comandos | Mas intuitiva visualmente | +| Muy eficiente para tareas repetitivas | Mas comoda para tareas visuales | +| Esencial para servidores y desarrollo | Comun en uso cotidiano | + +--- + +## Sistema de Archivos + +En Linux, todo el sistema de archivos se organiza como un arbol que comienza en la **raiz** (`/`). No existen letras de unidad como `C:\` o `D:\` en Windows; todo cuelga de `/`. + +### Directorios principales + +``` +/ +├── home/ # Directorios personales de los usuarios +├── etc/ # Archivos de configuracion del sistema +├── usr/ # Programas y librerias instalados +├── tmp/ # Archivos temporales (se borran al reiniciar) +├── var/ # Datos variables (logs, bases de datos, etc.) +├── bin/ # Comandos esenciales del sistema +├── sbin/ # Comandos de administracion del sistema +├── dev/ # Archivos de dispositivos (hardware) +├── opt/ # Software adicional/opcional +├── root/ # Directorio personal del usuario root +└── proc/ # Informacion sobre los procesos en ejecucion +``` + +El directorio personal de cada usuario esta en `/home/nombre_usuario`. Cuando abris una terminal, generalmente empezas en este directorio, representado por el simbolo `~`. + +### Rutas absolutas vs relativas + +- **Ruta absoluta**: Comienza desde la raiz (`/`). Siempre empieza con `/`. + ```bash + /home/usuario/Documentos/archivo.txt + ``` + +- **Ruta relativa**: Es relativa al directorio en el que te encontras actualmente. + ```bash + Documentos/archivo.txt + ``` + +Existen dos directorios especiales que se usan en rutas relativas: +- `.` (punto): Representa el directorio actual. +- `..` (dos puntos): Representa el directorio padre (un nivel arriba). + +```bash +# Si estas en /home/usuario +./Documentos # Equivale a /home/usuario/Documentos +../otro_usuario # Equivale a /home/otro_usuario +``` + +--- + +## Comandos Basicos de Navegacion + +### `pwd` - Mostrar directorio actual + +El comando `pwd` (*print working directory*) muestra la ruta completa del directorio en el que te encontras. + +```bash +$ pwd +/home/usuario +``` + +### `ls` - Listar contenido de un directorio + +El comando `ls` (*list*) muestra los archivos y carpetas dentro de un directorio. + +```bash +$ ls +Documentos Descargas Escritorio Imagenes Musica +``` + +#### Flags utiles de `ls` + +- **`ls -l`** (formato largo): Muestra informacion detallada de cada archivo. + ```bash + $ ls -l + total 20 + drwxr-xr-x 2 usuario usuario 4096 mar 15 10:00 Documentos + -rw-r--r-- 1 usuario usuario 256 mar 14 09:30 notas.txt + ``` + +- **`ls -a`** (all): Muestra archivos ocultos (los que empiezan con `.`). + ```bash + $ ls -a + . .. .bashrc .config Documentos notas.txt + ``` + +- **`ls -h`** (human-readable): Muestra tamaños en formato legible (KB, MB, GB). Se usa junto con `-l`. + ```bash + $ ls -lh + total 20K + drwxr-xr-x 2 usuario usuario 4.0K mar 15 10:00 Documentos + -rw-r--r-- 1 usuario usuario 256 mar 14 09:30 notas.txt + ``` + +**Tip**: Podes combinar flags. Por ejemplo, `ls -la` muestra todos los archivos (incluidos ocultos) en formato largo. `ls -lah` agrega el formato legible de tamaños. + +### `cd` - Cambiar de directorio + +El comando `cd` (*change directory*) te permite moverte entre directorios. + +```bash +$ cd Documentos # Entra al directorio Documentos +$ cd /home/usuario # Va a una ruta absoluta +$ cd .. # Sube un nivel (directorio padre) +$ cd ~ # Va al directorio home del usuario +$ cd - # Vuelve al directorio anterior +$ cd # Sin argumentos, va al home (igual que cd ~) +``` + +### `clear` - Limpiar la terminal + +El comando `clear` limpia toda la pantalla de la terminal. Tambien podes usar el atajo `Ctrl + L`. + +```bash +$ clear +``` + +--- + +## Manipulacion de Archivos y Directorios + +### `mkdir` - Crear directorios + +El comando `mkdir` (*make directory*) crea uno o mas directorios. + +```bash +$ mkdir mi_carpeta # Crea un directorio +$ mkdir carpeta1 carpeta2 carpeta3 # Crea varios directorios +$ mkdir -p padre/hijo/nieto # Crea directorios anidados (crea los intermedios si no existen) +``` + +### `touch` - Crear archivos vacios + +El comando `touch` crea un archivo vacio o actualiza la fecha de modificacion de un archivo existente. + +```bash +$ touch archivo.txt # Crea un archivo vacio +$ touch archivo1.txt archivo2.txt # Crea varios archivos +``` + +### `cp` - Copiar archivos y directorios + +El comando `cp` (*copy*) copia archivos o directorios. + +```bash +$ cp archivo.txt copia.txt # Copia un archivo +$ cp archivo.txt Documentos/ # Copia un archivo a otro directorio +$ cp -r carpeta/ copia_carpeta/ # Copia un directorio y todo su contenido (-r = recursivo) +``` + +### `mv` - Mover o renombrar archivos + +El comando `mv` (*move*) mueve archivos/directorios o los renombra. + +```bash +$ mv archivo.txt Documentos/ # Mueve el archivo a Documentos +$ mv viejo_nombre.txt nuevo_nombre.txt # Renombra un archivo +$ mv carpeta/ /home/usuario/otra_ruta/ # Mueve un directorio +``` + +### `rm` - Eliminar archivos + +El comando `rm` (*remove*) elimina archivos. + +```bash +$ rm archivo.txt # Elimina un archivo +$ rm archivo1.txt archivo2.txt # Elimina varios archivos +``` + +> **ADVERTENCIA**: `rm` elimina archivos **permanentemente**. No van a una papelera de reciclaje. Una vez eliminados, no se pueden recuperar facilmente. + +### `rmdir` - Eliminar directorios vacios + +El comando `rmdir` (*remove directory*) elimina directorios, pero solo si estan vacios. + +```bash +$ rmdir carpeta_vacia/ +``` + +### `rm -r` - Eliminar directorios con contenido + +Para eliminar un directorio que tiene archivos adentro, se usa `rm` con el flag `-r` (recursivo). + +```bash +$ rm -r carpeta/ # Elimina la carpeta y todo su contenido +``` + +> **CUIDADO con `rm -rf`**: El flag `-f` (*force*) fuerza la eliminacion sin pedir confirmacion. El comando `rm -rf` es extremadamente peligroso porque elimina todo sin preguntar. **NUNCA** ejecutes `rm -rf /` ya que esto borraria todo el sistema. Siempre verifica dos veces lo que estas eliminando antes de ejecutar este comando. + +**Tip**: Si queres que `rm` te pida confirmacion antes de eliminar cada archivo, usa el flag `-i`: +```bash +$ rm -ri carpeta/ +rm: ¿eliminar directorio 'carpeta/archivo.txt'? s +``` + +--- + +## Visualizacion de Archivos + +> **Nota**: En el apunte de Bash se cubren `cat` con redirecciones y pipelines. Aca se describen los comandos enfocados en la visualizacion de contenido. + +### `cat` - Mostrar contenido completo + +El comando `cat` (*concatenate*) muestra el contenido completo de un archivo en la terminal. + +```bash +$ cat archivo.txt +Linea 1 del archivo +Linea 2 del archivo +Linea 3 del archivo +``` + +Es util para archivos cortos. Para archivos largos, es mejor usar `less`. + +### `head` - Mostrar las primeras lineas + +El comando `head` muestra las primeras 10 lineas de un archivo por defecto. + +```bash +$ head archivo.txt # Muestra las primeras 10 lineas +$ head -n 5 archivo.txt # Muestra las primeras 5 lineas +``` + +### `tail` - Mostrar las ultimas lineas + +El comando `tail` muestra las ultimas 10 lineas de un archivo por defecto. + +```bash +$ tail archivo.txt # Muestra las ultimas 10 lineas +$ tail -n 5 archivo.txt # Muestra las ultimas 5 lineas +$ tail -f log.txt # Muestra las ultimas lineas y queda "escuchando" nuevas lineas en tiempo real (util para logs) +``` + +### `less` - Navegar por archivos grandes + +El comando `less` permite navegar por un archivo de forma paginada (ideal para archivos largos). + +```bash +$ less archivo_largo.txt +``` + +Dentro de `less`: +- **Espacio** o **Page Down**: Avanza una pagina. +- **b** o **Page Up**: Retrocede una pagina. +- **Flechas arriba/abajo**: Navega linea por linea. +- **/texto**: Busca "texto" dentro del archivo. +- **n**: Va a la siguiente coincidencia de busqueda. +- **q**: Sale de `less`. + +### `wc` - Contar lineas, palabras y caracteres + +El comando `wc` (*word count*) cuenta lineas, palabras y caracteres de un archivo. + +```bash +$ wc archivo.txt + 50 200 1500 archivo.txt +# | | | +# lineas palabras bytes + +$ wc -l archivo.txt # Solo cuenta lineas +50 archivo.txt + +$ wc -w archivo.txt # Solo cuenta palabras +200 archivo.txt +``` + +--- + +## Busqueda + +### `find` - Buscar archivos y directorios + +El comando `find` permite buscar archivos y directorios en el sistema de archivos. + +```bash +$ find . -name "archivo.txt" # Busca "archivo.txt" en el directorio actual y subdirectorios +./Documentos/archivo.txt + +$ find /home -name "*.txt" # Busca todos los archivos .txt en /home +/home/usuario/notas.txt +/home/usuario/Documentos/archivo.txt + +$ find . -type d -name "config" # Busca solo directorios (-type d) llamados "config" +./proyecto/config + +$ find . -type f -name "*.log" # Busca solo archivos (-type f) con extension .log +./var/app.log +``` + +Opciones utiles: +- `-name "patron"`: Busca por nombre (sensible a mayusculas/minusculas). +- `-iname "patron"`: Busca por nombre sin distinguir mayusculas de minusculas. +- `-type f`: Solo archivos. +- `-type d`: Solo directorios. + +### `grep` - Buscar texto dentro de archivos + +> **Nota**: En el apunte de Bash ya se cubre `grep` con pipelines y ejemplos avanzados. Aca se presenta un resumen basico. + +El comando `grep` busca lineas que coincidan con un patron de texto dentro de uno o mas archivos. + +```bash +$ grep "error" log.txt # Busca la palabra "error" en log.txt +Linea 15: Se produjo un error en el sistema + +$ grep -i "error" log.txt # Busca sin distinguir mayusculas/minusculas +Linea 15: Se produjo un error en el sistema +Linea 42: ERROR: conexion rechazada + +$ grep -r "TODO" ./proyecto/ # Busca recursivamente en todos los archivos del directorio +./proyecto/main.py: # TODO: implementar autenticacion +./proyecto/utils.py: # TODO: agregar validacion + +$ grep -n "funcion" script.py # Muestra el numero de linea de cada coincidencia +3: def funcion_principal(): +15: def funcion_auxiliar(): +``` + +### `which` - Encontrar la ubicacion de un comando + +El comando `which` muestra la ruta del ejecutable de un comando. + +```bash +$ which python +/usr/bin/python + +$ which git +/usr/bin/git + +$ which node +/home/usuario/.nvm/versions/node/v18.0.0/bin/node +``` + +Es util para saber que version de un programa se esta ejecutando o si un programa esta instalado. + +--- + +## Permisos + +En Linux, cada archivo y directorio tiene **permisos** que determinan quien puede leer, escribir o ejecutar ese recurso. Esto es fundamental para la seguridad del sistema. + +### Entendiendo la salida de `ls -l` + +```bash +$ ls -l +-rw-r--r-- 1 usuario grupo 4096 mar 15 10:00 archivo.txt +drwxr-xr-x 2 usuario grupo 4096 mar 15 10:00 carpeta/ +``` + +Desglosemos la primera columna (`-rw-r--r--`): + +``` +- rw- r-- r-- +| | | | +| | | └── Permisos para "otros" (other) +| | └────── Permisos para el "grupo" (group) +| └────────── Permisos para el "dueño" (user/owner) +└───────────── Tipo: - = archivo, d = directorio, l = enlace simbolico +``` + +Cada grupo de tres caracteres puede contener: +- **r** (*read*): Permiso de lectura. +- **w** (*write*): Permiso de escritura. +- **x** (*execute*): Permiso de ejecucion. +- **-**: Sin permiso. + +**Ejemplo**: `-rwxr-xr--` +- El dueño puede leer, escribir y ejecutar (`rwx`). +- El grupo puede leer y ejecutar (`r-x`). +- Otros solo pueden leer (`r--`). + +### `chmod` - Cambiar permisos + +El comando `chmod` (*change mode*) cambia los permisos de un archivo o directorio. + +#### Modo simbolico + +```bash +$ chmod u+x script.sh # Agrega permiso de ejecucion al dueño (u = user) +$ chmod g+w archivo.txt # Agrega permiso de escritura al grupo (g = group) +$ chmod o-r archivo.txt # Quita permiso de lectura a otros (o = other) +$ chmod a+r archivo.txt # Agrega permiso de lectura a todos (a = all) +``` + +- `u` = user (dueño), `g` = group (grupo), `o` = other (otros), `a` = all (todos) +- `+` = agregar permiso, `-` = quitar permiso, `=` = establecer permiso exacto + +#### Modo numerico (octal) + +Cada permiso tiene un valor numerico: +- **r** = 4 +- **w** = 2 +- **x** = 1 + +Se suman los valores para cada grupo (dueño, grupo, otros): + +```bash +$ chmod 755 script.sh # rwxr-xr-x (dueño: 7=4+2+1, grupo: 5=4+1, otros: 5=4+1) +$ chmod 644 archivo.txt # rw-r--r-- (dueño: 6=4+2, grupo: 4, otros: 4) +$ chmod 700 privado.sh # rwx------ (solo el dueño tiene acceso total) +``` + +**Valores comunes**: +| Octal | Permisos | Uso tipico | +|---|---|---| +| `755` | `rwxr-xr-x` | Scripts ejecutables, directorios | +| `644` | `rw-r--r--` | Archivos de texto, configuracion | +| `700` | `rwx------` | Archivos privados, scripts personales | +| `600` | `rw-------` | Archivos sensibles (claves SSH, etc.) | + +### `chown` - Cambiar propietario + +El comando `chown` (*change owner*) cambia el dueño y/o grupo de un archivo. + +```bash +$ sudo chown usuario archivo.txt # Cambia el dueño +$ sudo chown usuario:grupo archivo.txt # Cambia dueño y grupo +$ sudo chown -R usuario:grupo carpeta/ # Cambia dueño y grupo recursivamente +``` + +> **Nota**: Cambiar el propietario de archivos generalmente requiere permisos de administrador (`sudo`). + +--- + +## Otros Comandos Utiles + +### `man` - Manual de comandos + +El comando `man` (*manual*) muestra la documentacion detallada de cualquier comando. Es tu mejor amigo cuando no recordas como funciona algo. + +```bash +$ man ls # Muestra el manual del comando ls +$ man chmod # Muestra el manual de chmod +``` + +Dentro de `man`, la navegacion es igual que en `less` (espacio para avanzar, `q` para salir). + +**Tip**: Si necesitas una explicacion rapida, muchos comandos soportan el flag `--help`: +```bash +$ ls --help +``` + +### `whoami` - Mostrar usuario actual + +```bash +$ whoami +usuario +``` + +### `echo` - Imprimir texto + +El comando `echo` imprime texto en la terminal. + +```bash +$ echo "Hola mundo" +Hola mundo + +$ echo $HOME # Imprime el valor de una variable de entorno +/home/usuario +``` + +### `date` - Mostrar fecha y hora + +```bash +$ date +lun mar 15 10:30:00 ART 2026 +``` + +### `history` - Historial de comandos + +El comando `history` muestra los ultimos comandos que ejecutaste. + +```bash +$ history + 1 cd Documentos + 2 ls -la + 3 mkdir proyecto + 4 cd proyecto + 5 git init +``` + +**Tip**: Podes ejecutar un comando anterior del historial usando `!` seguido del numero: +```bash +$ !3 # Ejecuta el comando numero 3 (mkdir proyecto) +``` + +### `alias` - Crear atajos de comandos + +El comando `alias` permite crear atajos para comandos que usas frecuentemente. + +```bash +$ alias ll="ls -la" # Crea un alias +$ ll # Ahora "ll" ejecuta "ls -la" + +$ alias cls="clear" # Otro ejemplo +``` + +> **Nota**: Los alias creados en la terminal se pierden al cerrar la sesion. Para hacerlos permanentes, agregalos al archivo de configuracion de tu shell (`~/.bashrc`, `~/.zshrc`, etc.). + +### `sudo` - Ejecutar como administrador + +El comando `sudo` (*superuser do*) permite ejecutar comandos con permisos de administrador (root). Se te pedira la contrasena del usuario. + +```bash +$ sudo apt update # Actualizar lista de paquetes (en Ubuntu/Debian) +$ sudo rm /etc/archivo_protegido # Eliminar un archivo del sistema +``` + +> **CUIDADO**: Usar `sudo` te da acceso total al sistema. Un comando incorrecto con `sudo` puede causar daños serios. Siempre verifica el comando antes de ejecutarlo con permisos de administrador. + +--- + +## Atajos de Terminal + +Estos atajos de teclado te van a hacer mucho mas eficiente al trabajar con la terminal: + +### Navegacion y edicion + +| Atajo | Descripcion | +|---|---| +| **Tab** | Autocompleta nombres de archivos, directorios y comandos. Si hay varias opciones, presiona Tab dos veces para ver todas. | +| **Flecha arriba / abajo** | Navega por el historial de comandos anteriores. | +| **Ctrl + A** | Mueve el cursor al inicio de la linea. | +| **Ctrl + E** | Mueve el cursor al final de la linea. | +| **Ctrl + U** | Borra todo desde el cursor hasta el inicio de la linea. | +| **Ctrl + K** | Borra todo desde el cursor hasta el final de la linea. | +| **Ctrl + W** | Borra la palabra anterior al cursor. | + +### Control de procesos + +| Atajo | Descripcion | +|---|---| +| **Ctrl + C** | Cancela/interrumpe el comando que se esta ejecutando. | +| **Ctrl + D** | Cierra la terminal (envia señal de fin de archivo / EOF). | +| **Ctrl + Z** | Suspende el proceso actual (lo manda a segundo plano). | + +### Otros + +| Atajo | Descripcion | +|---|---| +| **Ctrl + L** | Limpia la pantalla (equivalente a `clear`). | +| **Ctrl + R** | Busqueda inversa en el historial. Empezas a escribir y te muestra comandos anteriores que coincidan. Muy util para encontrar comandos que ejecutaste hace tiempo. | + +### Ejemplo de uso de Ctrl + R + +``` +$ (Ctrl + R) +(reverse-i-search)`git': git push origin main +``` + +Al presionar `Ctrl + R` y empezar a escribir "git", la terminal te muestra el ultimo comando que contiene "git". Podes seguir presionando `Ctrl + R` para ver coincidencias anteriores, y cuando encuentres el comando que buscabas, presiona `Enter` para ejecutarlo. diff --git a/docs/Material/Apuntes/regex.md b/docs/Material/Apuntes/regex.md new file mode 100644 index 00000000..398ed4b7 --- /dev/null +++ b/docs/Material/Apuntes/regex.md @@ -0,0 +1,747 @@ +--- +sidebar_position: 4 +--- + +# Expresiones Regulares (Regex) + +Las **expresiones regulares** (en ingles *regular expressions*, abreviado **regex** o **regexp**) son secuencias de caracteres que definen un **patron de busqueda**. Se utilizan para buscar, validar, extraer y reemplazar texto dentro de cadenas de caracteres. + +Son una herramienta fundamental en programacion y administracion de sistemas, ya que permiten trabajar con texto de forma extremadamente flexible y poderosa. + +## Casos de uso en el mundo real + +- **Validar datos de entrada**: verificar que un email, telefono o DNI tenga el formato correcto. +- **Buscar patrones en archivos**: encontrar todas las direcciones IP en un log de servidor. +- **Reemplazar texto**: cambiar todas las ocurrencias de una palabra por otra en un archivo. +- **Extraer informacion**: obtener todos los enlaces de una pagina HTML. +- **Filtrar lineas**: mostrar solo las lineas de un archivo que cumplan cierta condicion. + +--- + +## Herramientas para practicar + +Antes de empezar, es muy recomendable tener a mano estas herramientas: + +### regex101.com + +[https://regex101.com/](https://regex101.com/) es una herramienta online e interactiva que permite escribir una expresion regular y ver en tiempo real que partes del texto coinciden. Ademas explica paso a paso que hace cada parte de la expresion. + +> Se recomienda usar esta herramienta mientras se resuelven los ejercicios de la Guia 2. + +### grep en la terminal + +`grep` es un comando de la terminal que permite buscar patrones (expresiones regulares) dentro de archivos. Se vera en detalle mas adelante en este apunte. + +```bash +grep "patron" archivo.txt +``` + +### sed en la terminal + +`sed` (*stream editor*) es un comando que permite buscar y reemplazar texto en archivos usando expresiones regulares. Tambien se vera en detalle mas adelante. + +```bash +sed 's/patron/reemplazo/g' archivo.txt +``` + +--- + +## Caracteres Literales y Metacaracteres + +### Caracteres literales + +Un **caracter literal** es simplemente un caracter que se busca tal cual. Por ejemplo, la expresion regular `hola` busca exactamente la secuencia de caracteres `h`, `o`, `l`, `a`. + +``` +Texto: "hola mundo, hola a todos" +Regex: hola +Matches: "hola" (2 coincidencias) +``` + +### Metacaracteres + +Los **metacaracteres** son caracteres que tienen un significado especial dentro de una expresion regular. No se interpretan de forma literal, sino que cumplen funciones especificas. + +Los metacaracteres son: + +``` +. ^ $ * + ? { } [ ] \ | ( ) +``` + +| Metacaracter | Significado | +|---|---| +| `.` | Cualquier caracter (excepto salto de linea) | +| `^` | Inicio de linea | +| `$` | Fin de linea | +| `*` | Cero o mas repeticiones del elemento anterior | +| `+` | Una o mas repeticiones del elemento anterior | +| `?` | Cero o una repeticion del elemento anterior | +| `{n}` | Exactamente *n* repeticiones | +| `[abc]` | Clase de caracteres (cualquiera de los listados) | +| `\` | Escape (para usar un metacaracter como literal) | +| `\|` | Alternancia (uno u otro) | +| `(abc)` | Grupo de captura | + +Si necesitas buscar uno de estos caracteres de forma literal, debes **escaparlo** con `\`. Esto se explica en detalle mas adelante. + +--- + +## Clases de Caracteres + +Las clases de caracteres permiten definir un **conjunto de caracteres** de los cuales se quiere buscar **uno cualquiera**. Se escriben entre corchetes `[ ]`. + +### Sintaxis basica + +| Expresion | Descripcion | Ejemplo de match | +|---|---|---| +| `[abc]` | Coincide con `a`, `b` o `c` | En `"banco"` matchea `b`, `a` | +| `[aeiou]` | Cualquier vocal minuscula | En `"hola"` matchea `o`, `a` | +| `[0-9]` | Cualquier digito del 0 al 9 | En `"abc123"` matchea `1`, `2`, `3` | +| `[a-z]` | Cualquier letra minuscula | En `"Hola"` matchea `o`, `l`, `a` | +| `[A-Z]` | Cualquier letra mayuscula | En `"Hola"` matchea `H` | +| `[a-zA-Z]` | Cualquier letra (minuscula o mayuscula) | En `"H0la"` matchea `H`, `l`, `a` | +| `[a-zA-Z0-9]` | Cualquier letra o digito | En `"H0la!"` matchea `H`, `0`, `l`, `a` | + +### Negacion con `^` + +Cuando el simbolo `^` aparece **dentro de los corchetes** como primer caracter, niega la clase. Es decir, coincide con cualquier caracter que **no** este en el conjunto. + +| Expresion | Descripcion | Ejemplo de match | +|---|---|---| +| `[^abc]` | Cualquier caracter que NO sea `a`, `b` ni `c` | En `"banco"` matchea `n`, `o` | +| `[^0-9]` | Cualquier caracter que NO sea un digito | En `"abc123"` matchea `a`, `b`, `c` | +| `[^a-z]` | Cualquier caracter que NO sea minuscula | En `"Hola1!"` matchea `H`, `1`, `!` | + +> **Atencion**: el `^` dentro de `[ ]` significa negacion. Fuera de `[ ]`, el `^` significa inicio de linea. Son significados distintos. + +### Ejemplo practico + +``` +Texto: "La computadora tiene 16 GB de RAM y 512 GB de almacenamiento." +Regex: [0-9] +Matches: 1, 6, 5, 1, 2 +``` + +``` +Texto: "Hola Mundo" +Regex: [A-Z] +Matches: H, M +``` + +--- + +## Metacaracteres Predefinidos + +Muchas clases de caracteres se usan tan seguido que tienen **atajos** (shortcuts). Estos son los metacaracteres predefinidos: + +| Metacaracter | Equivalente | Descripcion | +|---|---|---| +| `\d` | `[0-9]` | Cualquier digito | +| `\D` | `[^0-9]` | Cualquier caracter que NO sea un digito | +| `\w` | `[a-zA-Z0-9_]` | Cualquier caracter de "palabra" (letras, digitos, guion bajo) | +| `\W` | `[^a-zA-Z0-9_]` | Cualquier caracter que NO sea de "palabra" | +| `\s` | `[ \t\n\r]` | Cualquier espacio en blanco (espacio, tabulacion, salto de linea) | +| `\S` | `[^ \t\n\r]` | Cualquier caracter que NO sea espacio en blanco | +| `.` | (casi todo) | Cualquier caracter excepto salto de linea (`\n`) | + +### Ejemplos + +``` +Texto: "Tengo 3 gatos y 12 perros" +Regex: \d +Matches: 3, 1, 2 +``` + +``` +Texto: "Tengo 3 gatos y 12 perros" +Regex: \d+ +Matches: 3, 12 +``` + +``` +Texto: "hola mundo" +Regex: \w+ +Matches: "hola", "mundo" +``` + +``` +Texto: "hola mundo" +Regex: \s +Matches: " " (el espacio entre las dos palabras) +``` + +### El punto `.` + +El punto es uno de los metacaracteres mas usados. Coincide con **cualquier caracter** excepto el salto de linea. + +``` +Texto: "gato, gato, rato, pato" +Regex: .ato +Matches: "gato", "gato", "rato", "pato" +``` + +``` +Texto: "El perro corre rapido" +Regex: .o +Matches: "ro", "co", " r" (espacio + r), "do" +``` + +> **Importante**: si queres buscar un punto literal (`.`), debes escaparlo con `\`: `\.` + +--- + +## Cuantificadores + +Los cuantificadores indican **cuantas veces** debe aparecer el elemento que los precede. + +| Cuantificador | Significado | Ejemplo Regex | Texto | Matches | +|---|---|---|---|---| +| `*` | Cero o mas veces | `go*l` | "gl gol gool goool" | `gl`, `gol`, `gool`, `goool` | +| `+` | Una o mas veces | `go+l` | "gl gol gool goool" | `gol`, `gool`, `goool` | +| `?` | Cero o una vez | `colou?r` | "color colour" | `color`, `colour` | +| `{n}` | Exactamente *n* veces | `\d{3}` | "12 123 1234" | `123`, `123` (de 1234) | +| `{n,}` | Al menos *n* veces | `\d{2,}` | "1 12 123 1234" | `12`, `123`, `1234` | +| `{n,m}` | Entre *n* y *m* veces | `\d{2,4}` | "1 12 123 12345" | `12`, `123`, `1234` | + +### Ejemplos detallados + +**El `*` (cero o mas)**: + +``` +Texto: "ac abc abbc abbbc" +Regex: ab*c +Matches: "ac", "abc", "abbc", "abbbc" +``` + +Matchea `a` seguido de cero o mas `b`, seguido de `c`. + +**El `+` (una o mas)**: + +``` +Texto: "ac abc abbc abbbc" +Regex: ab+c +Matches: "abc", "abbc", "abbbc" +``` + +Matchea `a` seguido de una o mas `b`, seguido de `c`. No matchea `ac` porque necesita al menos una `b`. + +**El `?` (cero o una)**: + +``` +Texto: "color colour" +Regex: colou?r +Matches: "color", "colour" +``` + +La `u` es opcional. + +**El `{n}` (exactamente n)**: + +``` +Texto: "Las clases de Intro comienzan a las 7:30 AM." +Regex: \d{2} +Matches: "30" +``` + +Busca exactamente 2 digitos consecutivos. + +**El `{n,m}` (entre n y m)**: + +``` +Texto: "a1 ab12 abc123 abcd1234 abcde12345" +Regex: \d{2,4} +Matches: "12", "123", "1234", "1234" (de 12345) +``` + +Busca entre 2 y 4 digitos consecutivos. + +--- + +## Anclas + +Las anclas **no matchean caracteres**, sino que matchean **posiciones** dentro del texto. Sirven para indicar donde debe estar el patron. + +| Ancla | Descripcion | +|---|---| +| `^` | Inicio de la linea | +| `$` | Fin de la linea | +| `\b` | Limite de palabra (*word boundary*) | +| `\B` | NO limite de palabra | + +### `^` - Inicio de linea + +``` +Texto: + "La computadora tiene 16 GB" + "Las clases de Intro comienzan a las 7:30" + "El gato salto sobre la mesa" + +Regex: ^L +Matches: "L" (de "La"), "L" (de "Las") +``` + +Solo matchea la `L` cuando esta al inicio de la linea. No matchea la `L` que pueda aparecer en medio de una linea. + +### `$` - Fin de linea + +``` +Texto: + "Que dia tan bonito para un paseo por la montana!" + "Cuantas palabras puede contener esta oracion?" + +Regex: !$ +Matches: "!" (al final de la primera linea) +``` + +Solo matchea el `!` cuando esta al final de la linea. + +### `\b` - Limite de palabra + +El limite de palabra (`\b`) es la posicion entre un caracter de palabra (`\w`) y un caracter que no es de palabra (`\W`), o el inicio/fin de la cadena. + +``` +Texto: "El perro corre rapido por el parque" +Regex: \bel\b +Matches: "el" (la segunda ocurrencia, no "El" por diferencia de mayusculas) +``` + +``` +Texto: "El perro corre rapido por el parque" +Regex: \bp\w+ +Matches: "perro", "por", "parque" +``` + +En el ultimo ejemplo, `\bp\w+` busca palabras que comiencen con `p`: el `\b` asegura que la `p` esta al inicio de una palabra, y `\w+` matchea el resto de los caracteres de la palabra. + +> **Nota**: `\b` es muy util para la Guia 2. Por ejemplo, para encontrar "las ocurrencias de la letra c al comienzo de la palabra" se puede usar `\bc`. + +--- + +## Grupos y Alternancia + +### Grupos de captura `( )` + +Los parentesis `( )` agrupan partes de una expresion regular. Esto tiene dos funciones principales: + +1. **Agrupar**: aplicar cuantificadores a un grupo de caracteres en vez de a uno solo. +2. **Capturar**: guardar el texto matcheado por el grupo para poder reutilizarlo. + +``` +Texto: "abcabc" +Regex: (abc)+ +Matches: "abcabc" +``` + +Sin los parentesis, `abc+` matchearia `ab` seguido de una o mas `c`. + +``` +Texto: "jajaja" +Regex: (ja)+ +Matches: "jajaja" +``` + +### Alternancia `|` + +El operador `|` funciona como un **"o" logico**. Permite matchear una cosa **u** otra. + +``` +Texto: "Tengo un gato y un perro" +Regex: gato|perro +Matches: "gato", "perro" +``` + +Se puede combinar con grupos para crear alternativas dentro de un patron mas grande: + +``` +Texto: "manzana, banana, pera, mandarina" +Regex: man(zana|darina) +Matches: "manzana", "mandarina" +``` + +### Referencia hacia atras (backreference) + +Cuando se captura un grupo con `( )`, se puede hacer referencia al texto capturado usando `\1`, `\2`, etc., donde el numero indica el orden del grupo. + +``` +Texto: "abcabc" +Regex: (abc)\1 +Matches: "abcabc" +``` + +`\1` se refiere al texto que matcheo el primer grupo `(abc)`, es decir, busca que se repita exactamente lo mismo. + +Esto es especialmente util para encontrar, por ejemplo, contrasenas que comiencen y terminen con el mismo caracter: + +``` +Regex: ^(.).*\1$ +``` + +- `^` - Inicio de la cadena +- `(.)` - Captura el primer caracter (cualquiera) +- `.*` - Cualquier cantidad de caracteres en el medio +- `\1` - El mismo caracter que se capturo al principio +- `$` - Fin de la cadena + +--- + +## Escape de Caracteres Especiales + +Como vimos, los metacaracteres (`. ^ $ * + ? { } [ ] \ | ( )`) tienen significados especiales. Si queres buscar uno de estos caracteres **de forma literal**, necesitas **escaparlo** con una barra invertida `\`. + +| Quiero buscar | Escribo | +|---|---| +| Un punto `.` | `\.` | +| Un signo de pregunta `?` | `\?` | +| Un signo de exclamacion `!` | `!` (no es metacaracter, no necesita escape) | +| Un asterisco `*` | `\*` | +| Un parentesis `(` | `\(` | +| Un corchete `[` | `\[` | +| Una barra invertida `\` | `\\` | +| Un signo dolar `$` | `\$` | +| Un signo mas `+` | `\+` | + +### Ejemplo practico + +``` +Texto: "El perro corre rapido por el parque." +Regex: \. +Matches: "." (solo el punto literal al final) +``` + +Si usaras `.` sin escapar, matchearia **todos** los caracteres del texto. + +``` +Texto: "El precio es $50.99" +Regex: \$\d+\.\d+ +Matches: "$50.99" +``` + +Aca se escapa el `$` (para que no sea "fin de linea") y el `.` (para que no sea "cualquier caracter"). + +--- + +## Uso con `grep` + +`grep` (*Global Regular Expression Print*) es un comando de la terminal que busca lineas que coincidan con un patron (expresion regular) dentro de archivos. + +### Sintaxis basica + +```bash +grep [opciones] "patron" archivo +``` + +### Modos de expresion regular + +| Flag | Descripcion | +|---|---| +| (sin flag) | Expresiones regulares basicas (BRE). Algunos metacaracteres como `+`, `?`, `{`, `}`, `\|`, `(`, `)` deben escaparse con `\`. | +| `-E` | Expresiones regulares extendidas (ERE). Los metacaracteres `+`, `?`, `{`, `}`, `\|`, `(`, `)` funcionan sin escape. **Recomendado.** | +| `-P` | Expresiones regulares de Perl (PCRE). Soporta `\d`, `\w`, `\s`, `\b` y mas. El mas completo. | + +> **Recomendacion**: para la mayoria de los casos, usa `grep -P` ya que soporta todos los metacaracteres vistos en este apunte, como `\d`, `\w`, `\b`, etc. + +### Flags comunes + +| Flag | Descripcion | Ejemplo | +|---|---|---| +| `-i` | Ignorar mayusculas/minusculas | `grep -i "hola" archivo.txt` | +| `-c` | Contar cantidad de lineas que matchean | `grep -c "hola" archivo.txt` | +| `-v` | Invertir: mostrar lineas que NO matchean | `grep -v "hola" archivo.txt` | +| `-o` | Mostrar solo la parte que matchea (no toda la linea) | `grep -o "hola" archivo.txt` | +| `-n` | Mostrar el numero de linea | `grep -n "hola" archivo.txt` | +| `-l` | Mostrar solo los nombres de archivos que contienen matches | `grep -l "hola" *.txt` | +| `-w` | Matchear solo palabras completas | `grep -w "el" archivo.txt` | + +### Ejemplos con archivos de la Guia 2 + +Suponiendo que tenemos el archivo `oraciones.txt`: + +```bash +# Encontrar las ocurrencias de la letra "s" +grep -o "s" oraciones.txt + +# Encontrar todos los digitos +grep -P -o "\d" oraciones.txt + +# Encontrar las ocurrencias del punto literal +grep -o "\." oraciones.txt + +# Encontrar la letra "c" al comienzo de una palabra +grep -P -o "\bc" oraciones.txt + +# Encontrar palabras que comienzan con "p" +grep -P -o "\bp\w+" oraciones.txt + +# Lineas que comienzan con "L" +grep "^L" oraciones.txt + +# Lineas que terminan con "!" +grep '!$' oraciones.txt +``` + +### Combinando grep con pipes + +`grep` se puede combinar con otros comandos usando pipes (`|`): + +```bash +# Primero extraer las contrasenas y luego filtrar las alfanumericas +cat contraseñas.csv | grep -P -o "(?<=,)[^,]+" | grep -P "^[a-zA-Z0-9]+$" +``` + +--- + +## Uso con `sed` + +`sed` (*Stream Editor*) es un comando que permite buscar y reemplazar texto en archivos o flujos de datos usando expresiones regulares. + +### Sintaxis basica de sustitucion + +```bash +sed 's/patron/reemplazo/flags' archivo +``` + +- `s` indica que es una sustitucion. +- `patron` es la expresion regular a buscar. +- `reemplazo` es el texto con el que se reemplaza. +- `flags` son opcionales (por ejemplo, `g` para reemplazar todas las ocurrencias en cada linea). + +### Flags comunes de sed + +| Flag | Descripcion | +|---|---| +| `g` | Reemplazar **todas** las ocurrencias en cada linea (sin `g`, solo reemplaza la primera) | +| `i` | Ignorar mayusculas/minusculas (en GNU sed) | +| `-i` | Editar el archivo **in place** (modifica el archivo directamente) | +| `-E` | Usar expresiones regulares extendidas | + +### Ejemplos + +```bash +# Reemplazar "gato" por "perro" en un archivo (primera ocurrencia por linea) +sed 's/gato/perro/' archivo.txt + +# Reemplazar TODAS las ocurrencias de "gato" por "perro" +sed 's/gato/perro/g' archivo.txt + +# Eliminar todos los digitos de un archivo +sed 's/[0-9]//g' archivo.txt + +# Reemplazar directamente en el archivo (cuidado: modifica el original) +sed -i 's/viejo/nuevo/g' archivo.txt + +# Usar expresiones regulares extendidas +sed -E 's/[0-9]+/NUMERO/g' archivo.txt +``` + +### Usando grupos de captura en sed + +Se pueden usar grupos `( )` en el patron y referenciarlos con `\1`, `\2`, etc. en el reemplazo: + +```bash +# Invertir nombre y apellido separados por coma +sed -E 's/([a-zA-Z]+),([a-zA-Z]+)/\2 \1/g' archivo.txt +# "Garcia,Juan" -> "Juan Garcia" +``` + +--- + +## Lookahead y Lookbehind (avanzado) + +Estas son aserciones que permiten verificar que algo este **antes** o **despues** del patron, sin incluirlo en el match. Son utiles en los ejercicios avanzados de la Guia 2. + +| Sintaxis | Nombre | Descripcion | +|---|---|---| +| `(?=...)` | Lookahead positivo | Verifica que lo que sigue coincide con `...` | +| `(?!...)` | Lookahead negativo | Verifica que lo que sigue NO coincide con `...` | +| `(?<=...)` | Lookbehind positivo | Verifica que lo que precede coincide con `...` | +| `(? Para practicar de forma interactiva, visita [https://regex101.com/](https://regex101.com/) y selecciona el flavor **PCRE2** o **Python** para tener disponibles todos los tokens vistos en este apunte. diff --git a/docs/Material/Guias/04_Git.md b/docs/Material/Guias/04_Git.md new file mode 100644 index 00000000..27ab7bc9 --- /dev/null +++ b/docs/Material/Guias/04_Git.md @@ -0,0 +1,90 @@ +# Guia 4 - Git + +> **Nota:** Se recomienda crear un repositorio de prueba para practicar estos ejercicios. De esta manera, pueden experimentar libremente sin riesgo de afectar proyectos reales. Para una experiencia visual e interactiva del manejo de ramas, se recomienda utilizar [Learn Git Branching](https://learngitbranching.js.org/?locale=es_AR). + +> **Importante:** Antes de comenzar con los ejercicios de trabajo remoto, asegurarse de tener configurada la SSH Key. En la seccion "Apuntes" hay un tutorial detallado. + +### Introductorios + +1. Configurar Git en tu computadora estableciendo tu nombre de usuario y tu correo electronico de manera global. Verificar la configuracion con `git config --list`. + +2. Crear una carpeta llamada `mi-proyecto`, ingresar a ella e inicializar un repositorio Git. Verificar que se haya creado la carpeta `.git`. + +3. Dentro del repositorio creado en el ejercicio anterior, crear un archivo llamado `notas.txt` con algun contenido. Usar `git status` para observar el estado del archivo. Luego, agregarlo al area de staging con `git add` y volver a ejecutar `git status` para ver la diferencia. + +4. Realizar un commit del archivo `notas.txt` con un mensaje descriptivo. Luego, ejecutar `git log` para ver el historial de commits y verificar que el commit fue registrado correctamente. + +5. Modificar el archivo `notas.txt` agregando una nueva linea de texto. Usar `git diff` para ver las diferencias antes de hacer el commit. Luego, agregar el archivo al staging y realizar un nuevo commit. + +6. Crear tres archivos nuevos: `archivo1.txt`, `archivo2.txt` y `archivo3.txt`. Agregar solo `archivo1.txt` y `archivo2.txt` al area de staging. Ejecutar `git status` para confirmar que `archivo3.txt` sigue sin ser rastreado. Realizar el commit unicamente de los dos archivos preparados. + +7. Crear un archivo `.gitignore` en el repositorio que ignore todos los archivos con extension `.log` y una carpeta llamada `temp/`. Luego, crear un archivo `errores.log` y una carpeta `temp/` con algun archivo dentro. Verificar con `git status` que Git los esta ignorando correctamente. + +8. Ejecutar `git log` con las siguientes variantes y describir que informacion muestra cada una: + - `git log --oneline` + - `git log --oneline --graph` + - `git log --author="tu nombre"` + - `git log -3` + +### Ramas y Merge + +9. Crear una nueva rama llamada `nueva-funcionalidad` y cambiar a ella. Verificar en que rama te encontras con `git branch`. Luego, crear un archivo llamado `funcionalidad.txt`, hacer un commit y volver a la rama `main`. + +10. Desde la rama `main`, fusionar la rama `nueva-funcionalidad` usando `git merge`. Verificar con `git log --oneline --graph` que la fusion se realizo correctamente. + +11. Crear una rama llamada `experimento`. Cambiar a ella, crear un archivo `experimento.txt` y hacer un commit. Luego, volver a `main` y eliminar la rama `experimento` sin fusionarla. Investigar que flag del comando `git branch` se necesita para forzar la eliminacion. + +12. Simular un merge conflict realizando los siguientes pasos: + - Desde `main`, crear un archivo `datos.txt` con el contenido "Linea original" y hacer un commit. + - Crear una rama llamada `cambio-a` y en ella modificar `datos.txt` reemplazando el contenido por "Cambio desde rama A". Hacer commit. + - Volver a `main` y modificar `datos.txt` reemplazando el contenido por "Cambio desde main". Hacer commit. + - Intentar fusionar `cambio-a` en `main`. Resolver el conflicto manualmente eligiendo que contenido conservar, y luego completar el merge con `git add` y `git commit`. + +13. Crear dos ramas a partir de `main`: `feature-login` y `feature-registro`. Realizar al menos un commit en cada rama (en archivos distintos). Luego, fusionar ambas ramas en `main` de forma secuencial. Verificar el historial con `git log --oneline --graph`. + +### Trabajo remoto + +14. Crear un repositorio nuevo y vacio en GitHub. Luego, en tu repositorio local de prueba, agregar el repositorio remoto con `git remote add origin ` y subir la rama `main` con `git push -u origin main`. Verificar en GitHub que los archivos se subieron correctamente. + +15. Clonar un repositorio publico de GitHub (por ejemplo, uno de un companero o cualquier repositorio de practica). Explorar su historial de commits con `git log --oneline` y listar las ramas remotas con `git branch -r`. + +16. En el repositorio remoto creado en el ejercicio 14, crear un archivo directamente desde la interfaz web de GitHub. Luego, en tu repositorio local, ejecutar `git fetch` para traer los cambios sin fusionarlos. Observar con `git status` y `git log origin/main` que los cambios estan disponibles. Finalmente, ejecutar `git pull` para integrarlos a tu rama local. + +17. Crear una nueva rama local llamada `feature-remota`, realizar al menos un commit en ella, y subirla al repositorio remoto con `git push -u origin feature-remota`. Verificar en GitHub que la rama fue creada correctamente. + +### Colaboracion + +18. Hacer un **fork** de un repositorio publico en GitHub (puede ser el de un companero). Clonar tu fork a tu computadora, crear una rama, realizar un cambio y hacer un commit. Subir la rama a tu fork y abrir un **Pull Request** hacia el repositorio original desde la interfaz de GitHub. Describir los cambios realizados en el cuerpo del PR. + +19. En el repositorio de GitHub del ejercicio 14, crear un **Issue** describiendo una mejora o error ficticio. Asignarle una etiqueta (label) y una descripcion clara. Luego, crear una rama que resuelva ese issue, hacer el cambio correspondiente, subir la rama y abrir un Pull Request que mencione el issue (por ejemplo, "Resuelve #1" en la descripcion). + +20. Intercambiar repositorios con un companero. Clonar el repositorio del companero, crear una rama, realizar un cambio y abrir un Pull Request. El companero debe revisar el PR (dejando al menos un comentario con sugerencias o aprobacion) y luego fusionarlo desde GitHub. + +### Desafios + +21. Realizar el siguiente flujo completo de trabajo colaborativo: + - Crear un repositorio en GitHub con un archivo `README.md` inicial. + - Clonar el repositorio a tu computadora. + - Crear una rama `develop` y dentro de ella crear un archivo `app.txt`. + - Hacer commit y push de `develop`. + - Desde `develop`, crear una rama `feature-header` y modificar `app.txt` agregando una seccion "Header". Hacer commit y push. + - Abrir un Pull Request de `feature-header` hacia `develop` en GitHub y fusionarlo. + - En tu computadora, cambiar a `develop` y ejecutar `git pull` para traer los cambios fusionados. + +22. Investigar el comando `git stash`. En tu repositorio de prueba, modificar un archivo sin hacer commit. Luego, guardar los cambios temporalmente con `git stash`, cambiar de rama, volver a la rama original y recuperar los cambios con `git stash pop`. Describir en que situaciones resulta util este comando. + +23. Usar el comando `git log` con el flag `--diff-filter` para encontrar en que commit se agrego un archivo determinado. Ademas, usar `git log -p` para ver los cambios exactos introducidos en un commit especifico. Investigar para que sirve `git log --stat`. + +24. Crear un repositorio con la siguiente estructura de ramas y commits: + - `main` con un commit inicial. + - `feature-a` (creada desde `main`) con 2 commits. + - `feature-b` (creada desde `main`) con 1 commit que modifique el mismo archivo que `feature-a`. + - Fusionar `feature-a` en `main`. + - Intentar fusionar `feature-b` en `main`, resolver el conflicto resultante y completar el merge. + - Verificar el historial final con `git log --oneline --graph --all`. + +25. Investigar y practicar el uso de `git restore` y `git reset`. En un repositorio de prueba, realizar los siguientes pasos: + - Modificar un archivo y usar `git restore ` para deshacer los cambios antes de hacer staging. + - Modificar un archivo, agregarlo al staging con `git add`, y usar `git restore --staged ` para sacarlo del staging sin perder los cambios. + - Hacer un commit y luego usar `git reset --soft HEAD~1` para deshacer el commit manteniendo los cambios en staging. + - Explicar la diferencia entre `git reset --soft`, `git reset --mixed` y `git reset --hard`. diff --git a/docs/Material/Guias/05_Docker.md b/docs/Material/Guias/05_Docker.md new file mode 100644 index 00000000..ce6034a8 --- /dev/null +++ b/docs/Material/Guias/05_Docker.md @@ -0,0 +1,98 @@ +# Guia 5 - Docker + +> Para realizar estos ejercicios se necesita tener Docker instalado. Si no se cuenta con una instalacion local, se puede utilizar [Play with Docker](https://labs.play-with-docker.com/) desde el navegador. + +### Ejercicios introductorios + +1. Descargar la imagen oficial de `nginx` desde Docker Hub utilizando el comando `docker pull`. Verificar que la imagen se haya descargado correctamente listando las imagenes disponibles con `docker images`. + +2. Ejecutar un contenedor a partir de la imagen de `nginx` en modo interactivo. Luego, detener el contenedor con `docker stop` y verificar que ya no aparece en la lista de contenedores activos (`docker ps`). + +3. Listar todos los contenedores existentes (activos e inactivos) utilizando `docker ps -a`. Luego, eliminar todos los contenedores detenidos con `docker rm`. + +4. Ejecutar un contenedor de `ubuntu` en modo interactivo (`-it`) y dentro del contenedor ejecutar el comando `cat /etc/os-release` para verificar la version del sistema operativo. Salir del contenedor con `exit`. + +5. Descargar la imagen de `alpine` (una distribucion Linux muy liviana). Comparar el tamaño de la imagen de `alpine` con la de `ubuntu` utilizando `docker images`. Anotar la diferencia de tamaño. + +6. Ejecutar un contenedor de `nginx` en segundo plano (modo detached con `-d`) y asignarle el nombre `mi-servidor` utilizando la opcion `--name`. Verificar que esta corriendo con `docker ps`. Luego, ver los logs del contenedor con `docker logs mi-servidor`. + +### Imagenes y Contenedores + +7. Ejecutar un contenedor de `nginx` mapeando el puerto 8080 del host al puerto 80 del contenedor (`-p 8080:80`). Acceder desde el navegador a `http://localhost:8080` y verificar que se muestra la pagina por defecto de nginx. + +8. Ejecutar un contenedor de `postgres` (version 15) configurando las siguientes variables de entorno con la opcion `-e`: + - `POSTGRES_USER=alumno` + - `POSTGRES_PASSWORD=clave123` + - `POSTGRES_DB=universidad` + + Verificar que el contenedor se esta ejecutando correctamente. + +9. Crear una carpeta en el host llamada `mi-web` con un archivo `index.html` que contenga un mensaje de bienvenida. Ejecutar un contenedor de `nginx` montando esa carpeta como volumen (`-v`) en la ruta `/usr/share/nginx/html` del contenedor. Acceder desde el navegador y verificar que se muestra el contenido personalizado. + +10. Ejecutar un contenedor de `mysql` (version 8) con las siguientes configuraciones: + - Nombre del contenedor: `mi-mysql` + - Variable de entorno: `MYSQL_ROOT_PASSWORD=root123` + - Puerto mapeado: `3307:3306` + - Ejecucion en segundo plano + + Luego, conectarse al contenedor con `docker exec -it mi-mysql mysql -uroot -p` y crear una base de datos llamada `prueba`. + +11. Ejecutar un contenedor de `node` (version 20) en modo interactivo y dentro del contenedor ejecutar `node -e "console.log('Hola desde Docker')"`. Observar la salida por pantalla. + +12. Ejecutar un contenedor de `postgres` con un volumen con nombre (named volume) llamado `datos-postgres` montado en `/var/lib/postgresql/data`. Crear una tabla dentro de la base de datos. Luego, detener y eliminar el contenedor. Crear un nuevo contenedor de `postgres` montando el mismo volumen y verificar que la tabla sigue existiendo. + +### Dockerfile + +13. Crear un archivo `Dockerfile` que parta de la imagen de `node:20-alpine`, copie un archivo `app.js` (que imprima "Hola mundo desde mi contenedor") al directorio de trabajo `/app`, y defina como comando de inicio `node app.js`. Construir la imagen con `docker build` y ejecutar un contenedor a partir de ella. + +14. Crear un `Dockerfile` para una aplicacion web estatica que: + - Use `nginx:alpine` como imagen base + - Copie el contenido de una carpeta `sitio/` (con al menos un `index.html`) a `/usr/share/nginx/html` + - Exponga el puerto 80 + + Construir la imagen con el nombre `mi-sitio` y ejecutar un contenedor mapeando el puerto 8080 al 80. + +15. Crear un `Dockerfile` que parta de `python:3.11-slim`, establezca `/app` como directorio de trabajo (`WORKDIR`), copie un archivo `requirements.txt` y ejecute `pip install -r requirements.txt` para instalar las dependencias. Luego, copie el resto del codigo y defina como comando de inicio `python main.py`. El archivo `main.py` debe imprimir "Servidor iniciado" por pantalla. + +16. Construir una imagen a partir del Dockerfile del ejercicio anterior y etiquetarla con el nombre `mi-app-python` y la version `1.0` (`docker build -t mi-app-python:1.0 .`). Luego, listar las imagenes y verificar que aparece con el tag correcto. + +### Docker Compose + +17. Crear un archivo `docker-compose.yml` que defina un unico servicio llamado `web` que utilice la imagen de `nginx` y mapee el puerto `8080:80`. Levantar el servicio con `docker compose up -d` y verificar que funciona accediendo desde el navegador. + +18. Crear un archivo `docker-compose.yml` que defina dos servicios: + - `db`: un contenedor de `postgres:15` con las variables de entorno `POSTGRES_USER`, `POSTGRES_PASSWORD` y `POSTGRES_DB`. + - `adminer`: un contenedor de `adminer` con el puerto `8080:8080`. + + Levantar ambos servicios y acceder a Adminer desde el navegador para conectarse a la base de datos PostgreSQL. El nombre del servidor debe ser `db` (el nombre del servicio). + +19. Crear un archivo `docker-compose.yml` que defina los siguientes servicios: + - `app`: un servicio que se construya a partir de un `Dockerfile` local (utilizar `build: .`), mapee el puerto `3000:3000` y dependa del servicio `db` (`depends_on`). + - `db`: un servicio de `postgres:15` con un volumen persistente. + + Incluir una seccion `volumes:` a nivel raiz para definir el volumen con nombre. + +20. Crear un archivo `docker-compose.yml` que utilice un archivo `.env` para configurar las variables de entorno. El archivo `.env` debe contener: + - `DB_USER=admin` + - `DB_PASS=secreto` + - `DB_NAME=miapp` + + El archivo `docker-compose.yml` debe definir un servicio de `postgres` que use esas variables con la sintaxis `${VARIABLE}`. Levantar el servicio y verificar que la base de datos se creo con los valores definidos en el `.env`. + +### Desafios + +21. Crear un entorno completo de desarrollo para una aplicacion web con `docker-compose.yml` que incluya: + - Un servicio `frontend` con `nginx` sirviendo archivos estaticos desde una carpeta local. + - Un servicio `backend` construido desde un `Dockerfile` que ejecute una aplicacion en Node.js o Python. + - Un servicio `db` con `postgres` y un volumen persistente para los datos. + - Una red personalizada para que todos los servicios se comuniquen entre si. + +22. Escribir un `Dockerfile` multi-stage para una aplicacion Node.js que: + - En la primera etapa (`builder`), parta de `node:20`, copie el codigo, instale las dependencias y ejecute el build. + - En la segunda etapa, parta de `nginx:alpine` y copie los archivos generados en la etapa anterior a la carpeta de nginx. + + Construir la imagen y comparar su tamaño con una imagen que no utilice multi-stage. + +23. Simular un escenario de base de datos con datos iniciales. Crear un `docker-compose.yml` con un servicio de `postgres` que monte un archivo `init.sql` en la carpeta `/docker-entrypoint-initdb.d/` del contenedor. El archivo `init.sql` debe crear una tabla `alumnos` con columnas `id`, `nombre`, `apellido` y `legajo`, e insertar al menos 3 registros. Verificar que al levantar el contenedor la tabla ya contiene los datos. + +24. Dado el siguiente escenario: se tiene una aplicacion web compuesta por un frontend (React o HTML estatico), un backend (Node.js o Python) y una base de datos (PostgreSQL). Escribir todos los archivos necesarios (`Dockerfile`, `docker-compose.yml`, `.env`, codigo fuente minimo) para que con un unico comando (`docker compose up`) se levante toda la infraestructura y la aplicacion sea accesible desde el navegador. diff --git a/docs/Material/Guias/06_HTML_CSS.md b/docs/Material/Guias/06_HTML_CSS.md new file mode 100644 index 00000000..ab53581e --- /dev/null +++ b/docs/Material/Guias/06_HTML_CSS.md @@ -0,0 +1,84 @@ +# Guia 6 - HTML & CSS + +> **Nota:** Se recomienda validar el codigo HTML de cada ejercicio utilizando el validador oficial del W3C: [https://validator.w3.org/](https://validator.w3.org/). Esto permite detectar errores de sintaxis y asegurar que el documento cumple con los estandares web. +> +> Para profundizar en cualquier tema, consultar la documentacion de [MDN Web Docs](https://developer.mozilla.org/es/docs/Web). + +### HTML Basico + +1. Crear un archivo HTML con la estructura basica de un documento (``, ``, ``, ``). Dentro del ``, agregar un titulo `

` con tu nombre y un parrafo `

` que diga "Bienvenido a mi primera pagina web". + +2. Crear un archivo HTML que contenga los seis niveles de encabezado (`

` a `

`), cada uno con un texto que indique su nivel. Por ejemplo: "Este es un encabezado de nivel 1", "Este es un encabezado de nivel 2", etc. + +3. Crear un archivo HTML que contenga un parrafo con un enlace (``) que redirija a la pagina de Google y una imagen (``) debajo del parrafo. La imagen puede ser cualquier imagen de internet (usar una URL publica). No olvidar el atributo `alt` con una descripcion de la imagen. + +4. Crear un archivo HTML que contenga una lista no ordenada (`