# Guía para generar un proyecto desplegable en App Deployer — Salchimonster

> **Para la IA:** Este documento describe exactamente qué estructura debe tener
> el proyecto que generes. Al final encontrarás el checklist de entrega.
> El objetivo es producir un archivo `.zip` listo para subir al deployer.

---

## 1. Estructura del ZIP

El archivo comprimido debe llamarse `{nombre-app}.zip` y contener:

```
{nombre-app}.zip
├── frontend/
│   ├── index.html          ← OBLIGATORIO si hay frontend
│   ├── style.css
│   ├── app.js
│   └── assets/             (opcional: imágenes, fuentes, etc.)
├── backend/
│   ├── main.py             ← OBLIGATORIO — punto de entrada FastAPI
│   ├── requirements.txt    ← OBLIGATORIO — dependencias Python
│   ├── models.py           (opcional — modelos SQLAlchemy o similares)
│   ├── routers/            (opcional — sub-routers de FastAPI)
│   └── ...
└── database/
    └── app.db              ← opcional — SQLite preexistente o vacío
```

> Las carpetas pueden estar directamente en la raíz del ZIP o dentro de
> una carpeta contenedora (`{nombre-app}/frontend/...`). Ambos formatos
> son aceptados.

---

## 2. Reglas estrictas del backend

### 2.1 Framework y servidor

- **Solo FastAPI** (compatible con uvicorn). No Flask, no Django.
- El servidor de arranque es `uvicorn main:app --host 0.0.0.0 --port {PUERTO}`
  donde `{PUERTO}` es asignado automáticamente desde **9000** en adelante.
- **No incluyas** comandos de arranque ni `if __name__ == "__main__"` con uvicorn.

### 2.2 Punto de entrada

```python
# backend/main.py  ← el deployer arranca EXACTAMENTE este archivo
from fastapi import FastAPI

app = FastAPI()   # ← la variable DEBE llamarse `app`
```

### 2.3 CORS obligatorio

El frontend vive en un dominio diferente al backend, así que el backend
**siempre debe tener** CORS habilitado:

```python
from fastapi.middleware.cors import CORSMiddleware

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],        # en producción puedes restringir
    allow_methods=["*"],
    allow_headers=["*"],
)
```

### 2.4 requirements.txt

Incluye todas las dependencias. Mínimo para FastAPI:

```
fastapi
uvicorn[standard]
```

Si usas SQLite con SQLAlchemy:

```
fastapi
uvicorn[standard]
sqlalchemy
```

**No incluyas** versiones fijas a menos que sean críticas, para evitar
conflictos al instalar en el servidor.

---

## 3. Reglas del frontend

- Debe ser **HTML/CSS/JS puro** o un **build estático** de React/Vue/Svelte
  (carpeta `dist/` o `build/` copiada dentro de `frontend/`).
- `frontend/index.html` es el punto de entrada.
- Para SPAs (React/Vue), el servidor nginx ya tiene configurado
  `try_files $uri $uri/ /index.html` para que el routing del lado cliente funcione.
- Para llamar al backend desde el frontend usa el placeholder `%%API_URL%%`.
  El deployer lo reemplaza automáticamente con la URL real al desplegar:

```javascript
const API = "%%API_URL%%";   // ← el deployer reemplaza esto automáticamente
```

> **Alternativa aceptada:** si la IA usa `http://localhost:8000` o
> `http://127.0.0.1:8000`, el deployer también lo reemplaza solo.
> No uses `salchimonster.com` directamente — eso está bloqueado por Cloudflare.

---

## 4. Base de datos SQLite

- El archivo debe estar en `database/app.db`.
- El backend lo referencia así:

```python
from pathlib import Path

# Ruta al .db relativa al main.py del backend
DB_PATH = Path(__file__).parent.parent / "database" / "app.db"
```

- Si la base de datos no existe al desplegar, el backend debe crearla
  automáticamente con `CREATE TABLE IF NOT EXISTS`:

```python
import sqlite3

def init_db():
    with sqlite3.connect(str(DB_PATH)) as conn:
        conn.execute("""
            CREATE TABLE IF NOT EXISTS items (
                id    INTEGER PRIMARY KEY AUTOINCREMENT,
                name  TEXT NOT NULL,
                value TEXT
            )
        """)
        conn.commit()

init_db()   # ← llamar al importar el módulo
```

---

## 5. Ejemplo completo mínimo

### `backend/main.py`

```python
import sqlite3
from pathlib import Path
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel

app = FastAPI(title="Mi App")

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_methods=["*"],
    allow_headers=["*"],
)

DB_PATH = Path(__file__).parent.parent / "database" / "app.db"


def get_db():
    conn = sqlite3.connect(str(DB_PATH))
    conn.row_factory = sqlite3.Row
    return conn


def init_db():
    with get_db() as conn:
        conn.execute("""
            CREATE TABLE IF NOT EXISTS items (
                id    INTEGER PRIMARY KEY AUTOINCREMENT,
                name  TEXT NOT NULL,
                done  INTEGER DEFAULT 0
            )
        """)
        conn.commit()


init_db()


class ItemIn(BaseModel):
    name: str


@app.get("/")
def root():
    return {"status": "ok"}


@app.get("/items")
def list_items():
    with get_db() as conn:
        rows = conn.execute("SELECT * FROM items").fetchall()
    return [dict(r) for r in rows]


@app.post("/items")
def create_item(data: ItemIn):
    with get_db() as conn:
        cur = conn.execute("INSERT INTO items (name) VALUES (?)", (data.name,))
        conn.commit()
    return {"id": cur.lastrowid, "name": data.name}


@app.delete("/items/{item_id}")
def delete_item(item_id: int):
    with get_db() as conn:
        conn.execute("DELETE FROM items WHERE id = ?", (item_id,))
        conn.commit()
    return {"deleted": item_id}
```

### `backend/requirements.txt`

```
fastapi
uvicorn[standard]
pydantic
```

### `frontend/index.html`

```html
<!DOCTYPE html>
<html lang="es">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Mi App</title>
  <style>
    body { font-family: system-ui, sans-serif; max-width: 600px; margin: 2rem auto; padding: 0 1rem; }
    input { padding: 0.4rem 0.6rem; font-size: 1rem; border: 1px solid #ccc; border-radius: 5px; }
    button { padding: 0.4rem 1rem; cursor: pointer; border-radius: 5px; border: none; background: #2563eb; color: #fff; }
    li { display: flex; justify-content: space-between; padding: 0.4rem 0; border-bottom: 1px solid #eee; }
  </style>
</head>
<body>
  <h1>Mi App</h1>
  <div style="display:flex;gap:.5rem;margin-bottom:1rem">
    <input id="inp" placeholder="Nuevo ítem…" />
    <button onclick="addItem()">Agregar</button>
  </div>
  <ul id="list"></ul>

  <script>
    // ← Cambia este puerto por el asignado al desplegar
    const API = "http://salchimonster.com:9000";

    async function load() {
      const r = await fetch(`${API}/items`);
      const items = await r.json();
      document.getElementById("list").innerHTML = items.map(i =>
        `<li>${i.name} <button onclick="del(${i.id})">✕</button></li>`
      ).join("");
    }

    async function addItem() {
      const inp = document.getElementById("inp");
      if (!inp.value.trim()) return;
      await fetch(`${API}/items`, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ name: inp.value.trim() })
      });
      inp.value = "";
      load();
    }

    async function del(id) {
      await fetch(`${API}/items/${id}`, { method: "DELETE" });
      load();
    }

    load();
  </script>
</body>
</html>
```

---

## 6. URLs finales después del despliegue

Una vez subido el ZIP al deployer, la app queda en:

| Componente | URL |
|-----------|-----|
| **Frontend** | `http://bryan.salchimonster.com:{puerto-front}` |
| **Backend**  | `http://salchimonster.com:{puerto-back}` |

Cada app recibe **dos puertos consecutivos** desde el **9000** en adelante:
- Puerto N → backend (FastAPI)
- Puerto N+1 → frontend (nginx sirviendo archivos estáticos)

No se requieren registros DNS adicionales.

El `slug` es el nombre que se le da a la app en el formulario del deployer,
convertido a minúsculas con guiones (ej: `"Mi Lista"` → `mi-lista`).

---

## 7. Checklist antes de comprimir el ZIP

- [ ] `frontend/index.html` existe (si hay frontend)
- [ ] `backend/main.py` existe y tiene `app = FastAPI()`
- [ ] `backend/main.py` tiene CORS habilitado con `allow_origins=["*"]`
- [ ] `backend/requirements.txt` existe con todas las dependencias
- [ ] La base de datos se crea sola si no existe (`CREATE TABLE IF NOT EXISTS`)
- [ ] El backend **no** tiene `uvicorn.run()` ni `app.run()` en el código
- [ ] La ruta al `app.db` usa `Path(__file__).parent.parent / "database" / "app.db"`
- [ ] El ZIP no contiene la carpeta `venv/`, `__pycache__/`, ni `.env`

---

## 8. Cómo comprimir correctamente

```bash
# Desde la carpeta raíz del proyecto
zip -r mi-app.zip frontend/ backend/ database/ \
    --exclude "backend/venv/*" \
    --exclude "backend/__pycache__/*" \
    --exclude "*.pyc" \
    --exclude ".env"
```

O con la carpeta contenedora:

```bash
cd ..
zip -r mi-app.zip mi-app/ \
    --exclude "mi-app/backend/venv/*" \
    --exclude "mi-app/backend/__pycache__/*"
```
