# PRD: Migración Portal Estudiantil UNIR

## Stack propuesto: FastAPI (Python) + React SPA (Node.js/TypeScript) + PostgreSQL

---

## 1. Resumen Ejecutivo

Migrar el Portal Estudiantil UNIR — actualmente un monolito CodeIgniter 4 (PHP) con vistas server-side y un SPA parcial Vue 3 — a una arquitectura moderna de dos partes:

- **Backend**: API REST con Python + FastAPI, PostgreSQL unificado multitenant, JWT auth.
- **Frontend**: SPA con React + TypeScript + Vite, reemplazando todas las vistas PHP y el SPA admin actual.

**Motivación**: eliminar el acoplamiento PHP monolítico, unificar los 4 esquemas MySQL en un solo PostgreSQL con `tenant_id`, corregir problemas de seguridad (contraseñas en base64), y habilitar desarrollo paralelo frontend/backend con una API documentada (OpenAPI).

---

## 2. Estado Actual

| Aspecto | Actual |
|---|---|
| **Framework** | CodeIgniter 4 (PHP 7.2+) |
| **Frontend** | Vistas PHP server-rendered + SPA admin parcial (Vue 3 / Vuetify / Pinia) |
| **Base de datos** | 4 bases MySQL separadas por sede (default, centro, cabimas, cincodj) |
| **Auth estudiantes** | Sesiones PHP nativas, contraseñas `base64_encode` (texto plano) |
| **Auth admin** | Token temporal en tabla `ac_sesiones` con IP binding |
| **API** | REST parcial (solo módulo inscripciones); la mayoría de endpoints son rutas tradicionales |
| **Pagos** | 7+ gateways bancarios venezolanos integrados |
| **Chatbot** | Google Gemini con RAG sobre base de conocimiento local |
| **PDF** | FPDF personalizado para horarios y recibos |
| **Email** | PHPMailer vía Gmail SMTP |
| **CAPTCHA** | Google reCAPTCHA v2 |

**Módulos principales**: preinscripción, registro, inscripción/horario, pagos en línea, consulta de notas, horarios, olvido de clave, primer ingreso, chatbot IA, reportes admin, servicio comunitario.

---

## 3. Arquitectura Propuesta

```
┌──────────────────────────────────────────────────────────┐
│                    CLIENTES                               │
│  ┌─────────────────────┐  ┌────────────────────────────┐ │
│  │   React SPA         │  │   Otros clientes            │ │
│  │   (Estudiantes +    │  │   (app móvil, microservicios)│ │
│  │    Admin)            │  │                              │ │
│  └────────┬────────────┘  └─────────────┬────────────────┘ │
└───────────┼─────────────────────────────┼──────────────────┘
            │                             │
            ▼                             ▼
┌───────────────────────────────────────────────────────────┐
│                   API Gateway / Nginx                      │
│         CORS, rate limiting, static file serving           │
└───────────────────────┬───────────────────────────────────┘
                        │
                        ▼
┌───────────────────────────────────────────────────────────┐
│              BACKEND: FastAPI (Python 3.11+)               │
│                                                           │
│  ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │
│  │ Auth     │ │ Students │ │ Payments │ │ Chatbot       │ │
│  │ (JWT)    │ │ Enrollment│ │ Gateways │ │ (Gemini+RAG) │ │
│  └──────────┘ └──────────┘ └──────────┘ └──────────────┘ │
│  ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │
│  │ Reports  │ │ Pre-reg  │ │ Email    │ │ Community     │ │
│  │ (PDF)    │ │ (Docs)   │ │ Service  │ │ Service       │ │
│  └──────────┘ └──────────┘ └──────────┘ └──────────────┘ │
│                                                           │
│  ORM: SQLAlchemy 2.0 (async) + Alembic (migrations)      │
│  Schemas: Pydantic v2                                     │
│  Queue: Celery + Redis (email, PDF, export jobs)          │
└───────────────────────┬───────────────────────────────────┘
                        │
                        ▼
┌───────────────────────────────────────────────────────────┐
│              PostgreSQL 15+ (unificado)                    │
│                                                           │
│  Todas las tablas con columna branch_id (tenant)          │
│  Row-Level Security por branch_id                         │
│  PGroonga o pg_trgm para búsqueda full-text (chatbot)     │
│  File storage: MinIO (S3-compatible) o local              │
└───────────────────────────────────────────────────────────┘
```

**Separación de responsabilidades**:

- **Backend**: 100% API REST. No genera HTML. Documentado con OpenAPI (Swagger) automático.
- **Frontend**: 100% SPA. Consume la API. Sin lógica de negocio. Server-side rendering opcional (Next.js) para SEO de landing pública.
- **File storage**: MinIO o S3 para documentos subidos (preinscripción), con URLs firmadas temporales.

---

## 4. Stack Tecnológico

### 4.1 Backend (Python)

| Componente | Tecnología | Justificación |
|---|---|---|
| Framework | **FastAPI** 0.100+ | Alto rendimiento, OpenAPI automático, async nativo, validación con Pydantic |
| ORM | **SQLAlchemy 2.0** (async) | ORM maduro, soporte async, migraciones vía Alembic |
| Migraciones | **Alembic** | Versionado de esquema, autogeneración desde modelos |
| Validación | **Pydantic v2** | Schemas tipados, validación automática, integrado con FastAPI |
| Autenticación | **python-jose** + **passlib[bcrypt]** | JWT stateless, bcrypt para passwords |
| Tareas asíncronas | **Celery** + **Redis** | Emails, generación de PDF, exportaciones |
| PDF | **WeasyPrint** o **ReportLab** | Reemplaza FPDF con HTML/CSS o programático |
| Email | **fastapi-mail** o **aiosmtplib** | Envío async vía SMTP |
| Excel | **openpyxl** | Reemplaza PhpSpreadsheet |
| File storage | **boto3** (MinIO/S3) | Documentos de preinscripción |
| Chatbot | **google-generativeai** + LangChain (opcional) | Gemini API con RAG |
| Testing | **pytest** + **httpx** + **pytest-asyncio** | Tests unitarios y de integración |
| Linting | **ruff** | Linter y formateador rápido |

### 4.2 Frontend (Node.js)

| Componente | Tecnología | Justificación |
|---|---|---|
| Framework | **React 18+** con **TypeScript** | Ecosistema amplio, tipado estricto |
| Build tool | **Vite** | Desarrollo rápido, HMR instantáneo |
| UI Framework | **Mantine** o **Ant Design** | Componentes listos (tablas, forms, modales) optimizados para dashboards |
| Estado global | **Zustand** o **React Context** | Más ligero que Redux para esta escala |
| Ruteo | **React Router v6** | SPA routing estándar |
| HTTP Client | **Axios** + **React Query (TanStack)** | Cache, revalidación, estados de carga/error automáticos |
| Tablas | **TanStack Table v8** | Tablas server-side con orden, filtro, paginación |
| Formularios | **React Hook Form** + **Zod** | Validación tipada, integración con esquemas backend |
| Gráficos | **Recharts** | Dashboard de admin |
| PDF (cliente) | **@react-pdf/renderer** o **jsPDF** | Vista previa de horarios |
| Alertas | **Sonner** o **react-hot-toast** | Notificaciones toast |
| i18n | **react-i18next** | Soporte español/inglés |
| Testing | **Vitest** + **React Testing Library** | Tests unitarios e integración |

---

## 5. Diseño del Backend

### 5.1 Estructura de proyecto

```
backend/
├── alembic/                    # Migraciones
│   └── versions/
├── app/
│   ├── main.py                 # FastAPI app factory, middleware, routers
│   ├── config.py               # Settings con Pydantic (carga .env)
│   ├── database.py             # SQLAlchemy engine async, session factory
│   ├── dependencies.py         # Depends() reutilizables (auth, tenant, db)
│   ├── models/                 # SQLAlchemy ORM models
│   │   ├── base.py             # Base + tenant mixin (branch_id)
│   │   ├── auth/               # Usuarios, sesiones, perfiles, accesos
│   │   ├── students/           # ceestudi, personales, academica, etc.
│   │   ├── enrollment/         # ceestudiinsc, materias, pensum, secciones
│   │   ├── payments/           # Intenciones, pagos, gateways
│   │   ├── chatbot/            # Agentes, conocimiento, chats, mensajes
│   │   └── config/             # Settings, branches, periods
│   ├── schemas/                # Pydantic request/response
│   │   ├── auth.py
│   │   ├── students.py
│   │   ├── enrollment.py
│   │   ├── payments.py
│   │   └── chatbot.py
│   ├── routers/                # FastAPI APIRouter por dominio
│   │   ├── auth.py             # /api/v1/auth/*
│   │   ├── students.py         # /api/v1/students/*
│   │   ├── enrollment.py       # /api/v1/enrollment/*
│   │   ├── payments.py         # /api/v1/payments/*
│   │   ├── reports.py          # /api/v1/reports/*
│   │   ├── chatbot.py          # /api/v1/chatbot/*
│   │   ├── preregistration.py  # /api/v1/preregistration/*
│   │   └── admin.py            # /api/v1/admin/*
│   ├── services/               # Lógica de negocio
│   │   ├── auth_service.py
│   │   ├── enrollment_service.py
│   │   ├── payment_service.py
│   │   ├── schedule_builder_service.py
│   │   ├── chatbot_service.py
│   │   ├── pdf_service.py
│   │   ├── email_service.py
│   │   └── export_service.py
│   ├── integrations/           # APIs externas
│   │   ├── gemini.py
│   │   ├── recaptcha.py
│   │   ├── payment_gateways/
│   │   │   ├── base.py
│   │   │   ├── mercantil.py
│   │   │   ├── bnc.py
│   │   │   ├── banesco.py
│   │   │   ├── bdv.py
│   │   │   └── saldo_favor.py
│   │   └── storage.py          # MinIO/S3
│   └── utils/
│       ├── security.py         # JWT, password hashing
│       ├── pagination.py       # Paginación estandarizada
│       └── exceptions.py       # Exception handlers
├── tests/
│   ├── conftest.py
│   ├── test_auth.py
│   ├── test_enrollment.py
│   └── ...
├── celery_worker.py
├── Dockerfile
├── docker-compose.yml          # PostgreSQL + Redis + MinIO + app
├── pyproject.toml
└── .env.example
```

### 5.2 Modelo de datos (PostgreSQL unificado)

**Principio multitenant**: toda tabla relevante incluye `branch_id INTEGER NOT NULL`. PostgreSQL Row-Level Security (RLS) fuerza que cada tenant solo vea sus datos. La app establece `branch_id` vía contexto de request (header `Branch` o derivado del JWT del usuario).

**Tablas principales** (desde MySQL actual, unificadas):

```sql
-- Esquema core: auth y configuración central
branches            (id, name, db_alias, is_active)
users               (id, branch_id, cedula, username, password_hash, primer_ingreso, ...)
profiles            (id, name)
profile_access      (profile_id, access_id)
access_modules      (id, module_key, name)
sessions            (id, user_id, token, ip_address, expires_at)
config              (id, branch_id, key, value)

-- Esquema estudiantes
student_entities    (id, branch_id, cedula, first_name, last_name, ...)  -- ex ceestudi + aaentidad
student_users       (id, entity_id, user_id)  -- relación estudiante-usuario

-- Esquema preinscripción
pre_personals       (id, branch_id, cedula, first_name, last_name, ...)
pre_academic        (id, personal_id, high_school, grad_year, mention, ...)
pre_laboral         (id, personal_id, company, position, ...)
pre_representatives (id, personal_id, name, relation, ...)
pre_documents       (id, personal_id, doc_type, file_path, ...)
pre_payments        (id, personal_id, bank, amount, voucher_path, ...)
pre_careers         (id, personal_id, career_id, mention)
becados             (id, branch_id, cedula, authorized_by, ...)

-- Esquema inscripciones
careers             (id, branch_id, code, name, ...)          -- ex cecarrera
pensum              (id, career_id, period, total_uc)         -- ex cecarrerapens
pensum_courses      (id, pensum_id, course_id, semester, prerequisite_id)  -- ex cecarrerapensmater
courses             (id, branch_id, code, name, uc)           -- ex cemateria
sections            (id, branch_id, course_id, period_id, code, capacity, ...)  -- ex ceseccion
section_schedules   (id, section_id, day_of_week, start_time, end_time, classroom)
periods             (id, branch_id, name, start_date, end_date)  -- ex ceperiolect
payment_plans       (id, period_id, name, exchange_rate_id)    -- ex ceplanpagoperio
enrollments         (id, student_id, period_id, payment_plan_id, status, ...)  -- ex ceestudiinsc
enrollment_courses  (id, enrollment_id, section_id, uc, cost, ...)  -- ex ceestudiinscmater
enrollment_temp     (id, student_id, ...)                     -- ex ceestudiinsctemp

-- Esquema pagos
exchange_rates      (id, date, rate, currency)
payment_intents     (id, student_id, total_amount_bs, status, ...)
payment_intent_items(id, intent_id, type, reference_id, amount, ...)
payment_transactions(id, intent_id, gateway, gateway_ref, status, ...)
banks               (id, name, code)

-- Esquema notas
grades              (id, enrollment_course_id, grade_type, grade, qualifier, lapso)
grade_scales        (id, branch_id, numeric_grade, letter_grade)  -- ex ceescalanotas

-- Esquema chatbot
bot_agents          (id, name, system_prompt, is_active, ...)
bot_knowledge       (id, agent_id, tag, question, answer, embedding)
bot_configurations  (id, agent_id, key, value)
bot_chats           (id, user_id, agent_id, started_at, status)
bot_messages        (id, chat_id, role, content, tokens_used, ...)
bot_unanswered      (id, chat_id, question, created_at)
bot_token_usage     (id, user_id, month, tokens, exceeded)

-- Esquema servicio comunitario
community_services  (id, branch_id, student_id, project, status, ...)
```

### 5.3 API REST Design

**Convenciones**:
- Base URL: `/api/v1/`
- Paginación: `?page=1&per_page=25` → respuesta con `{ data: [], meta: { page, per_page, total, pages } }`
- Filtros: `?filter[field]=value&filter[field:op]=value` (op: eq, neq, gt, lt, like, in)
- Orden: `?sort=field` / `?sort=-field`
- Campos: `?fields=id,name,email`
- Autenticación: `Authorization: Bearer <jwt_token>`
- Tenant: `Branch: <branch_id>` header (requerido para admin multi-sede)
- Errores: `{ error: { code: "INVALID_CREDENTIALS", message: "...", details: [] } }`
- Export: `GET /api/v1/enrollments/export?format=csv` con `Accept` header

**Endpoints principales** (>80 endpoints):

| Grupo | Endpoints clave |
|---|---|
| **Auth** | `POST /auth/student/login`, `POST /auth/admin/login`, `POST /auth/register`, `POST /auth/forgot-password`, `POST /auth/reset-password`, `POST /auth/change-password`, `GET /auth/me` |
| **Estudiantes** | `GET /students/validate-cedula`, `GET /students/profile`, `PUT /students/profile` |
| **Preinscripción** | `POST /preregistration/personal`, `POST /preregistration/academic`, `POST /preregistration/documents`, `POST /preregistration/payment-proof`, `POST /preregistration/submit` |
| **Inscripciones** | `GET /enrollment/available-courses`, `GET /enrollment/sections`, `POST /enrollment/schedule`, `POST /enrollment/payment-plan`, `POST /enrollment/submit`, `DELETE /enrollment/:id`, `GET /enrollment/:id/schedule-pdf` |
| **Pagos** | `GET /payments/debts`, `POST /payments/intents`, `GET /payments/intents/:id`, `POST /payments/intents/:id/pay`, `GET /payments/gateways`, `POST /payments/callback/:gateway`, `GET /payments/history` |
| **Notas** | `GET /grades?period_id=:id`, `GET /grades/summary` |
| **Horarios** | `GET /schedules?period_id=:id`, `GET /schedules/pdf` |
| **Reportes** (admin) | `GET /admin/enrollments`, `GET /admin/enrollments/:id`, `GET /admin/enrollments/export`, `GET /admin/enrollments/:id/schedule-pdf`, `GET /admin/enrollments/courses`, `GET /admin/stats` |
| **Chatbot** | `GET /chatbot/agents`, `POST /chatbot/agents/:id/verify`, `POST /chatbot/chats`, `POST /chatbot/chats/:id/messages`, `GET /chatbot/chats/:id/messages` |
| **Catálogos** | `GET /catalog/careers`, `GET /catalog/periods`, `GET /catalog/banks`, `GET /catalog/branches` |
| **Servicio comunitario** | `GET /community-service`, `POST /community-service`, `GET /community-service/pdf` |
| **Config** | `GET /config/maintenance-status`, `GET /config/branch-settings` |

### 5.4 Mecanismo de tenant routing

Middleware de FastAPI que:
1. Lee header `Branch` (obligatorio para admin, opcional para estudiantes)
2. Si es estudiante, deriva `branch_id` del JWT
3. Establece `branch_id` en el contexto del request (vía `ContextVar`)
4. SQLAlchemy session usa RLS: `SET app.current_branch_id = :branch_id`

```python
# dependencies.py
async def get_current_branch(
    request: Request,
    branch_header: str | None = Header(None),
    current_user: User = Depends(get_current_user),
):
    if current_user.role == "admin":
        if not branch_header:
            raise HTTPException(400, "Branch header required for admin")
        return await validate_branch(branch_header)
    return current_user.branch_id  # desde JWT

async def get_db_session(branch_id: int = Depends(get_current_branch)):
    async with AsyncSession() as session:
        await session.execute(
            text("SELECT set_config('app.current_branch_id', :bid, false)"),
            {"bid": str(branch_id)}
        )
        yield session
```

### 5.5 JWT Auth (unificado)

**Reemplaza** los dos sistemas actuales (sesión PHP + token admin), con un solo flujo JWT:

**Student JWT payload**:
```json
{
  "sub": "user_uuid",
  "role": "student",
  "branch_id": 1,
  "entity_id": 12345,
  "cedula": "V12345678",
  "primer_ingreso": false,
  "exp": 1719260000
}
```

**Admin JWT payload**:
```json
{
  "sub": "user_uuid",
  "role": "admin",
  "profile": "Administrador",
  "access": ["md-admin-reportes", "md-admin-pagos"],
  "exp": 1719260000,
  "iat": 1719259400
}
```

- Access token: 30 min (student), 10 min (admin) con sliding refresh
- Refresh token: 7 días, almacenado en httpOnly cookie
- Blacklist de tokens en Redis para invalidación inmediata
- Rate limiting por IP + por user (slowapi o middleware propio)

---

## 6. Diseño del Frontend (React SPA)

### 6.1 Estructura de proyecto

```
frontend/
├── public/
├── src/
│   ├── api/                    # Cliente API + React Query hooks
│   │   ├── client.ts           # Axios instance con interceptors (JWT refresh, branch header)
│   │   ├── auth.ts             # useLogin, useRegister, useMe hooks
│   │   ├── enrollment.ts       # useAvailableCourses, useSections, etc.
│   │   ├── payments.ts
│   │   ├── chatbot.ts
│   │   └── admin.ts
│   ├── components/             # Componentes reutilizables
│   │   ├── ui/                 # Button, Input, DataTable, Modal, Toast, etc.
│   │   ├── layout/             # AppLayout, AuthLayout, Sidebar, Header
│   │   ├── forms/              # LoginForm, RegisterForm, ScheduleBuilder
│   │   ├── enrollment/         # CourseCard, SectionSelector, ScheduleGrid
│   │   ├── payments/           # DebtList, PaymentGatewaySelector, PaymentForm
│   │   ├── chatbot/            # ChatWindow, MessageBubble, AgentSelector
│   │   └── reports/            # EnrollmentTable, ExportButton
│   ├── hooks/                  # Custom hooks
│   │   ├── useAuth.ts
│   │   ├── useBranch.ts
│   │   └── useDebounce.ts
│   ├── pages/                  # Páginas (por rol)
│   │   ├── public/             # Landing, Login, Preinscripción, Registro
│   │   │   ├── LandingPage.tsx
│   │   │   ├── LoginPage.tsx
│   │   │   ├── RegisterPage.tsx
│   │   │   ├── ForgotPasswordPage.tsx
│   │   │   ├── PreRegistrationWizard/
│   │   │   │   ├── PersonalDataStep.tsx
│   │   │   │   ├── AcademicStep.tsx
│   │   │   │   ├── DocumentsStep.tsx
│   │   │   │   └── PaymentProofStep.tsx
│   │   │   └── ProfessorLoginPage.tsx
│   │   ├── student/            # Dashboard estudiante
│   │   │   ├── StudentDashboard.tsx
│   │   │   ├── EnrollmentPage.tsx
│   │   │   ├── ScheduleBuilderPage.tsx
│   │   │   ├── PaymentHistoryPage.tsx
│   │   │   ├── GradesPage.tsx
│   │   │   ├── SchedulePage.tsx
│   │   │   ├── ChatbotPage.tsx
│   │   │   ├── ProfilePage.tsx
│   │   │   └── FirstLoginSetup.tsx
│   │   ├── admin/              # Dashboard admin
│   │   │   ├── AdminDashboard.tsx
│   │   │   ├── EnrollmentReportPage.tsx
│   │   │   ├── EnrollmentDetailPage.tsx
│   │   │   ├── CourseReportPage.tsx
│   │   │   └── CommunityServicePage.tsx
│   │   └── errors/             # 404, 500, mantenimiento
│   ├── stores/                 # Zustand stores
│   │   ├── authStore.ts
│   │   ├── branchStore.ts
│   │   └── chatbotStore.ts
│   ├── routes/                 # React Router config
│   │   ├── AppRouter.tsx
│   │   ├── ProtectedRoute.tsx
│   │   └── RoleRoute.tsx
│   ├── types/                  # TypeScript types (generados de OpenAPI)
│   │   └── api.ts              # openapi-typescript auto-generado
│   ├── utils/
│   │   ├── constants.ts
│   │   └── formatters.ts
│   ├── App.tsx
│   └── main.tsx
├── index.html
├── vite.config.ts
├── tsconfig.json
├── package.json
├── Dockerfile
└── .env.example
```

### 6.2 Flujo de autenticación frontend

```
1. Usuario accede a /login
2. LoginForm → POST /api/v1/auth/student/login
3. Backend devuelve { access_token, refresh_token, user }
4. access_token → memoria (Zustand store)
5. refresh_token → httpOnly cookie (seteada por backend)
6. Axios interceptor:
   - Adjunta Authorization: Bearer <access_token>
   - Adjunta Branch: <branch_id> (solo admin)
   - Si 401 → intenta POST /api/v1/auth/refresh → nuevo access_token
   - Si refresh falla → redirect a /login
7. ProtectedRoute verifica existencia de access_token
8. RoleRoute verifica role (student vs admin)
```

### 6.3 Temas y diseño

- **Theme**: Claro/oscuro toggle, colores institucionales UNIR (verde/azul corporativo)
- **Responsivo**: Mobile-first para estudiantes (muchos acceden desde teléfono)
- **Accesibilidad**: WCAG 2.1 AA, labels en español, teclado navegable
- **Offline**: Service worker para cache de assets estáticos; indicador de conectividad

---

## 7. Plan de Migración de Datos

### 7.1 Estrategia

**Fase 1: Schema mapping y ETL**
1. Analizar cada tabla MySQL origen y mapear a tabla PostgreSQL destino
2. Agregar columna `branch_id` derivada del nombre de la base de datos origen
3. Normalizar: convertir `base64_encode(password)` a `bcrypt(password)`
4. ETL script en Python con `sqlalchemy` + `pymysql` → `asyncpg`

**Fase 2: Migración de datos**
- Script idempotente (puede correrse múltiples veces)
- Orden de migración: branches → users → estudiantes → carreras → períodos → pensum → inscripciones → pagos → chatbot
- Validación post-migración: conteo de registros por tabla, checksums

**Fase 3: Cutover**
- Período de paralelo: ambas apps corren, nueva app en subdominio `beta.unir.edu.ve`
- Switch DNS cuando UAT esté aprobado
- Rollback: mantener app PHP antigua en standby por 30 días

### 7.2 Transformaciones críticas

| Campo actual | Problema | Solución |
|---|---|---|
| `aausuario.clave` | `base64_encode(plaintext)` | En ETL: decodificar → generar `bcrypt(pass_original)` |
| `aausuario.primer_ingreso` | Flag booleano | Migrar tal cual; nueva app lo usa igual |
| `ac_sesiones.token_temporal` | Token simple con IP | Reemplazado por JWT con refresh token |
| 4 bases separadas | Sin tenant_id | Agregar `branch_id` según DB origen |
| Archivos en `public/documentos/` | Paths locales | Migrar a MinIO, actualizar referencias |

---

## 8. Mejoras de Seguridad (respecto al sistema actual)

| Vulnerabilidad actual | Corrección en nueva app |
|---|---|
| Contraseñas en base64 (equivalente a texto plano) | bcrypt con 12 rounds |
| Token admin sin firma criptográfica | JWT RS256 con clave privada |
| Sin rate limiting en login | Rate limit: 5 intentos/minuto/IP |
| Sin expiración en sesiones de estudiante | JWT con expiración corta + refresh token |
| CORS abierto solo al dominio UNIR | CORS middleware configurable por ambiente |
| Archivos servidos directamente | URLs firmadas temporales (MinIO presigned) |
| reCAPTCHA v2 | reCAPTCHA v3 (invisible, score-based) |
| Sin logs de auditoría | Tabla `audit_log` con acciones de usuarios |

---

## 9. Infraestructura y Deploy

### 9.1 Docker Compose (desarrollo)

```yaml
services:
  db:
    image: postgres:15
    environment:
      POSTGRES_DB: portal_estudiantil
      POSTGRES_USER: app
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    volumes:
      - pgdata:/var/lib/postgresql/data

  redis:
    image: redis:7-alpine

  minio:
    image: minio/minio
    command: server /data --console-address :9001

  backend:
    build: ./backend
    depends_on: [db, redis, minio]
    environment: *env
    ports: ["8000:8000"]

  worker:
    build: ./backend
    command: celery -A celery_worker worker -l info
    depends_on: [db, redis]

  frontend:
    build: ./frontend
    depends_on: [backend]
    ports: ["5173:5173"]  # vite dev
```

### 9.2 Producción

- **Backend**: Kubernetes o VPS con `uvicorn` + `gunicorn` workers
- **Frontend**: Nginx sirviendo build estático + reverse proxy a backend
- **SSL**: Let's Encrypt vía certbot o Traefik
- **CI/CD**: GitHub Actions → build Docker images → deploy

---

## 10. Migración por Fases

### Fase 1: Fundación (6-8 semanas)
- Setup de proyecto backend + frontend
- Migración de esquema PostgreSQL + Alembic inicial
- Script de ETL desde MySQL
- Auth module completo (login, registro, forgot password, JWT)

### Fase 2: Core Estudiantil (8-10 semanas)
- Módulo preinscripción (wizard completo + upload docs)
- Módulo inscripciones (schedule builder, pensum, secciones)
- Módulo notas (consulta por período)
- Módulo horarios (consulta + PDF)

### Fase 3: Pagos y Admin (6-8 semanas)
- Módulo pagos (intenciones, gateways, callback)
- Módulo reportes admin (tabla server-side, export)
- Dashboard admin con estadísticas

### Fase 4: Chatbot y Extras (4-6 semanas)
- Módulo chatbot (Gemini + RAG, agentes, historial)
- Módulo servicio comunitario (profesores)
- Help/Contact form
- Maintenance mode
- Landing page pública

### Fase 5: QA, UAT, Cutover (4 semanas)
- Test suite completo (pytest backend, vitest frontend)
- Pruebas de carga (Locust)
- UAT con usuarios reales
- Período paralelo (beta.unir.edu.ve)
- Cutover final y rollback plan

### Timeline total estimado: 28-36 semanas

---

## 11. Riesgos y Mitigaciones

| Riesgo | Impacto | Probabilidad | Mitigación |
|---|---|---|---|
| Pérdida de datos en ETL | Crítico | Baja | Validación exhaustiva, backups, checksums, dry-run |
| Gateways de pago incompatibles | Alto | Media | Testear cada gateway en sandbox antes de prod |
| Resistencia al cambio de usuarios | Medio | Media | UAT temprano, videos tutoriales, soporte telefónico |
| Lógica de negocio no documentada | Alto | Alta | Reverse engineering de código PHP + entrevistas con stakeholders |
| Dependencia de terceros (Gemini) | Medio | Baja | Abstracción detrás de interfaz, permitir cambio de provider |
| Regresiones en schedule builder | Alto | Media | Tests exhaustivos del algoritmo de prelaciones y conflictos horarios |
| Multi-tenancy incorrecta (datos mezclados) | Crítico | Baja | PostgreSQL RLS + tests de integración por tenant + code review obligatorio |

---

## 12. Métricas de Éxito

| Métrica | Objetivo |
|---|---|
| **Funcionalidad** | 100% de features actuales migradas y funcionando |
| **Seguridad** | 0 vulnerabilidades críticas/altas en OWASP ZAP scan |
| **Rendimiento** | API responses < 200ms p95, < 500ms para PDFs |
| **Carga** | Soportar 500 usuarios concurrentes sin degradación |
| **Cobertura** | >80% code coverage en backend, >70% en frontend |
| **Uptime** | 99.5% en horario hábil |
| **Rollback** | Capacidad de revertir a app antigua en < 1 hora |
| **Docs** | OpenAPI spec completa + manual de administrador |

---

## 13. Go/No-Go Decisiones

Al final de cada fase, revisión con stakeholders:

**Fase 1 Go/No-Go**:
- [ ] Auth funciona con JWT (login, refresh, logout)
- [ ] Migración de datos completa y validada (todas las tablas)
- [ ] PostgreSQL RLS funcionando con `branch_id`
- [ ] Ambientes dev + staging configurados

**Fase 2 Go/No-Go**:
- [ ] Preinscripción funcional (formulario completo + upload)
- [ ] Schedule builder genera horarios válidos
- [ ] Notas muestran datos correctos contra base actual
- [ ] 10 usuarios beta aprueban UX

**Fase 3 Go/No-Go**:
- [ ] Pagos procesados correctamente en sandbox de cada gateway
- [ ] Reportes admin coinciden con datos del sistema actual
- [ ] Export CSV/Excel funcional

**Fase 4 Go/No-Go**:
- [ ] Chatbot responde con precisión >80% en preguntas frecuentes
- [ ] Servicio comunitario funcional para profesores
- [ ] Test suite completa pasa
- [ ] Prueba de carga: 500 usuarios concurrentes sin errores

---

## 14. Apéndice: Comparativa de librerías (actual vs nueva)

| Funcionalidad | Actual (PHP) | Nueva (Python) |
|---|---|---|
| PDF horarios | FPDF personalizado | WeasyPrint (HTML→PDF) |
| PDF recibos | FPDF personalizado | WeasyPrint |
| Excel | PhpSpreadsheet | openpyxl |
| Email | PHPMailer (SMTP) | aiosmtplib / fastapi-mail |
| CAPTCHA | reCAPTCHA v2 | reCAPTCHA v3 (requests) |
| Chatbot IA | google-generativeai (PHP) | google-generativeai (Python) |
| File upload | move_uploaded_file | boto3 → MinIO presigned |
| Encriptación pagos | AES manual | cryptography (Fernet) |
| Logging | error_log | structlog / loguru |
| Debug | Kint | Rich / devtools |
| Testing | PHPUnit | pytest + pytest-asyncio |
| Queue/Scheduler | N/A (síncrono) | Celery + Redis |
| API docs | Manual | OpenAPI automático (Swagger UI) |

---

*Documento v1.0 — Preparado para revisión por stakeholders técnicos.*
*Próximo paso: estimación detallada de esfuerzo por módulo (story points).*
