# Documentación Técnica - Sistema de Gestión de Facturas 606

## 📋 Tabla de Contenidos

1. [Arquitectura del Sistema](#arquitectura-del-sistema)
2. [Backend - API Flask](#backend---api-flask)
3. [Frontend Web - React](#frontend-web---react)
4. [Frontend Móvil - React](#frontend-móvil---react)
5. [Base de Datos](#base-de-datos)
6. [Integración OCR](#integración-ocr)
7. [Seguridad](#seguridad)
8. [Despliegue](#despliegue)
9. [Monitoreo y Logs](#monitoreo-y-logs)
10. [APIs Externas](#apis-externas)

---

## Arquitectura del Sistema

### Visión General

El sistema utiliza una arquitectura de microservicios con separación clara entre frontend y backend:

```
┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
│   Frontend Web  │    │ Frontend Móvil  │    │     Backend     │
│   (React)       │    │   (React)       │    │    (Flask)      │
│   Puerto 5173   │    │   Puerto 5174   │    │   Puerto 5000   │
└─────────────────┘    └─────────────────┘    └─────────────────┘
         │                       │                       │
         └───────────────────────┼───────────────────────┘
                                 │
                    ┌─────────────────┐
                    │   Base de Datos │
                    │    (SQLite)     │
                    └─────────────────┘
```

### Tecnologías Utilizadas

| Componente | Tecnología | Versión |
|------------|------------|---------|
| Backend | Flask | 2.3.3 |
| Frontend Web | React | 18.2.0 |
| Frontend Móvil | React | 18.2.0 |
| Base de Datos | SQLite | 3.x |
| ORM | SQLAlchemy | 2.0.x |
| Autenticación | JWT | PyJWT 2.8.0 |
| OCR | OpenAI Vision API | GPT-4 Vision |
| Build Tool | Vite | 5.x |
| CSS Framework | Tailwind CSS | 3.x |
| UI Components | shadcn/ui | Latest |

### Principios de Diseño

- **Separación de Responsabilidades**: Frontend y backend completamente separados
- **API RESTful**: Comunicación estándar HTTP/JSON
- **Responsive Design**: Interfaces adaptables a diferentes dispositivos
- **Seguridad por Capas**: Autenticación, autorización y validación
- **Escalabilidad**: Arquitectura preparada para crecimiento
- **Mantenibilidad**: Código modular y bien documentado

---

## Backend - API Flask

### Estructura del Proyecto

```
invoice-606-backend/
├── src/
│   ├── main.py                 # Punto de entrada de la aplicación
│   ├── models/                 # Modelos de datos
│   │   ├── user.py
│   │   ├── company.py
│   │   ├── invoice.py
│   │   └── expense_type.py
│   ├── routes/                 # Rutas de la API
│   │   ├── auth.py
│   │   ├── user.py
│   │   ├── company.py
│   │   ├── invoice.py
│   │   └── reports.py
│   ├── services/               # Lógica de negocio
│   │   ├── ocr_service.py
│   │   └── report_606_service.py
│   ├── tests/                  # Pruebas unitarias
│   └── database/               # Archivos de base de datos
└── requirements.txt            # Dependencias
```

### Configuración Principal

```python
# main.py
from flask import Flask
from flask_cors import CORS
from src.models.user import db

app = Flask(__name__)
app.config['SECRET_KEY'] = 'asdf#FGSgvasgf$5$WGT'

# Configuración CORS
CORS(app, supports_credentials=True, resources={
    r"/api/*": {
        "origins": ["http://localhost:5173", "http://localhost:5174"],
        "methods": ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
        "allow_headers": ["Content-Type", "Authorization"]
    }
})

# Configuración de base de datos
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///database/app.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
```

### Modelos de Datos

#### Usuario (User)
```python
class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    first_name = db.Column(db.String(50), nullable=False)
    last_name = db.Column(db.String(50), nullable=False)
    username = db.Column(db.String(50), unique=True, nullable=False)
    password_hash = db.Column(db.String(255), nullable=False)
    email = db.Column(db.String(100))
    role = db.Column(db.String(20), nullable=False)  # admin, accountant, employee
    company_id = db.Column(db.Integer, db.ForeignKey('company.id'))
    is_active = db.Column(db.Boolean, default=True)
    created_at = db.Column(db.DateTime, default=datetime.utcnow)
```

#### Empresa (Company)
```python
class Company(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(200), nullable=False)
    rnc = db.Column(db.String(20), unique=True, nullable=False)
    address = db.Column(db.Text)
    phone = db.Column(db.String(20))
    email = db.Column(db.String(100))
    is_active = db.Column(db.Boolean, default=True)
    created_at = db.Column(db.DateTime, default=datetime.utcnow)
```

#### Factura (Invoice)
```python
class Invoice(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    supplier_name = db.Column(db.String(200), nullable=False)
    rnc = db.Column(db.String(20))
    ncf = db.Column(db.String(20), nullable=False)
    date = db.Column(db.Date, nullable=False)
    amount = db.Column(db.Numeric(12, 2), nullable=False)
    itbis = db.Column(db.Numeric(12, 2), default=0)
    status = db.Column(db.String(20), default='pending')
    description = db.Column(db.Text)
    company_id = db.Column(db.Integer, db.ForeignKey('company.id'))
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
    expense_type_id = db.Column(db.Integer, db.ForeignKey('expense_type.id'))
    created_at = db.Column(db.DateTime, default=datetime.utcnow)
```

### Endpoints de la API

#### Autenticación

| Método | Endpoint | Descripción | Parámetros |
|--------|----------|-------------|------------|
| POST | `/api/auth/login` | Iniciar sesión | username, password |
| GET | `/api/auth/me` | Información del usuario actual | - |
| POST | `/api/auth/logout` | Cerrar sesión | - |

**Ejemplo de Login:**
```json
POST /api/auth/login
{
  "username": "contador1",
  "password": "contador123"
}

Response:
{
  "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
  "user": {
    "id": 1,
    "first_name": "Juan",
    "last_name": "Pérez",
    "username": "contador1",
    "role": "accountant",
    "company": {
      "id": 1,
      "name": "Empresa Demo S.A."
    }
  }
}
```

#### Gestión de Facturas

| Método | Endpoint | Descripción | Parámetros |
|--------|----------|-------------|------------|
| GET | `/api/invoices` | Listar facturas | page, per_page, status, expense_type |
| POST | `/api/invoices` | Crear factura | Datos de la factura |
| GET | `/api/invoices/{id}` | Obtener factura específica | - |
| PUT | `/api/invoices/{id}` | Actualizar factura | Datos actualizados |
| DELETE | `/api/invoices/{id}` | Eliminar factura | - |

**Ejemplo de Crear Factura:**
```json
POST /api/invoices
{
  "supplier_name": "Proveedor Demo",
  "rnc": "123456789",
  "ncf": "B0100000001",
  "date": "2024-08-25",
  "amount": 10000.00,
  "itbis": 1800.00,
  "expense_type_id": 1,
  "description": "Compra de suministros"
}
```

#### Gestión de Empresas (Solo Super Admin)

| Método | Endpoint | Descripción | Parámetros |
|--------|----------|-------------|------------|
| GET | `/api/companies` | Listar empresas | - |
| POST | `/api/companies` | Crear empresa | Datos de la empresa |
| PUT | `/api/companies/{id}` | Actualizar empresa | Datos actualizados |
| DELETE | `/api/companies/{id}` | Eliminar empresa | - |

#### Reportes 606

| Método | Endpoint | Descripción | Parámetros |
|--------|----------|-------------|------------|
| GET | `/api/reports/606` | Generar reporte 606 | start_date, end_date, format |
| GET | `/api/reports/606/preview` | Vista previa del reporte | start_date, end_date |

### Servicios

#### Servicio OCR
```python
class OCRService:
    def __init__(self):
        self.client = OpenAI()
    
    def extract_invoice_data(self, image_file):
        """Extrae datos de factura usando OpenAI Vision API"""
        response = self.client.chat.completions.create(
            model="gpt-4-vision-preview",
            messages=[{
                "role": "user",
                "content": [
                    {"type": "text", "text": "Extrae los datos de esta factura..."},
                    {"type": "image_url", "image_url": {"url": image_url}}
                ]
            }]
        )
        return self.parse_response(response)
```

#### Servicio de Reportes 606
```python
class Report606Service:
    def generate_report(self, company_id, start_date, end_date, format='txt'):
        """Genera reporte 606 en formato DGII"""
        invoices = self.get_invoices_for_period(company_id, start_date, end_date)
        
        if format == 'txt':
            return self.generate_txt_format(invoices)
        elif format == 'excel':
            return self.generate_excel_format(invoices)
        elif format == 'csv':
            return self.generate_csv_format(invoices)
```

### Autenticación JWT

```python
def generate_token(user):
    payload = {
        'user_id': user.id,
        'username': user.username,
        'role': user.role,
        'company_id': user.company_id,
        'exp': datetime.utcnow() + timedelta(hours=24)
    }
    return jwt.encode(payload, app.config['SECRET_KEY'], algorithm='HS256')

def verify_token(token):
    try:
        payload = jwt.decode(token, app.config['SECRET_KEY'], algorithms=['HS256'])
        return payload
    except jwt.ExpiredSignatureError:
        return None
    except jwt.InvalidTokenError:
        return None
```

### Middleware de Autorización

```python
def require_auth(f):
    @wraps(f)
    def decorated_function(*args, **kwargs):
        token = request.headers.get('Authorization')
        if not token:
            return jsonify({'error': 'Token requerido'}), 401
        
        token = token.replace('Bearer ', '')
        payload = verify_token(token)
        if not payload:
            return jsonify({'error': 'Token inválido'}), 401
        
        request.current_user = payload
        return f(*args, **kwargs)
    return decorated_function

def require_role(role):
    def decorator(f):
        @wraps(f)
        def decorated_function(*args, **kwargs):
            if request.current_user['role'] != role:
                return jsonify({'error': 'Acceso denegado'}), 403
            return f(*args, **kwargs)
        return decorated_function
    return decorator
```

---


## Frontend Web - React

### Estructura del Proyecto

```
invoice-606-web/
├── src/
│   ├── App.jsx                 # Componente principal
│   ├── App.css                 # Estilos globales
│   ├── components/             # Componentes React
│   │   ├── LoginForm.jsx
│   │   ├── Layout.jsx
│   │   ├── Dashboard.jsx
│   │   ├── InvoicesPage.jsx
│   │   ├── ReportsPage.jsx
│   │   ├── UsersPage.jsx
│   │   ├── UploadInvoicePage.jsx
│   │   ├── SuperAdminDashboard.jsx
│   │   └── CompaniesPage.jsx
│   ├── hooks/                  # Custom hooks
│   │   └── useAuth.js
│   ├── lib/                    # Utilidades
│   │   └── api.js
│   └── components/ui/          # Componentes UI base
├── index.html
├── package.json
└── vite.config.js
```

### Configuración Principal

```javascript
// vite.config.js
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import path from 'path'

export default defineConfig({
  plugins: [react()],
  resolve: {
    alias: {
      "@": path.resolve(__dirname, "./src"),
    },
  },
  server: {
    port: 5173,
    host: true
  }
})
```

### Gestión de Estado

#### Hook de Autenticación
```javascript
// hooks/useAuth.js
import { useState, useEffect, createContext, useContext } from 'react';
import { api } from '../lib/api';

const AuthContext = createContext();

export const AuthProvider = ({ children }) => {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const token = localStorage.getItem('token');
    if (token) {
      api.defaults.headers.common['Authorization'] = `Bearer ${token}`;
      fetchUser();
    } else {
      setLoading(false);
    }
  }, []);

  const fetchUser = async () => {
    try {
      const response = await api.get('/auth/me');
      setUser(response.data);
    } catch (error) {
      localStorage.removeItem('token');
      delete api.defaults.headers.common['Authorization'];
    } finally {
      setLoading(false);
    }
  };

  const login = async (credentials) => {
    const response = await api.post('/auth/login', credentials);
    const { token, user } = response.data;
    
    localStorage.setItem('token', token);
    api.defaults.headers.common['Authorization'] = `Bearer ${token}`;
    setUser(user);
    
    return user;
  };

  const logout = () => {
    localStorage.removeItem('token');
    delete api.defaults.headers.common['Authorization'];
    setUser(null);
  };

  return (
    <AuthContext.Provider value={{ user, login, logout, loading }}>
      {children}
    </AuthContext.Provider>
  );
};

export const useAuth = () => useContext(AuthContext);
```

#### Cliente API
```javascript
// lib/api.js
import axios from 'axios';

export const api = axios.create({
  baseURL: 'http://localhost:5000/api',
  headers: {
    'Content-Type': 'application/json',
  },
});

// Interceptor para manejo de errores
api.interceptors.response.use(
  (response) => response,
  (error) => {
    if (error.response?.status === 401) {
      localStorage.removeItem('token');
      window.location.href = '/login';
    }
    return Promise.reject(error);
  }
);

// Métodos de API
export const invoiceAPI = {
  getAll: (params) => api.get('/invoices', { params }),
  create: (data) => api.post('/invoices', data),
  update: (id, data) => api.put(`/invoices/${id}`, data),
  delete: (id) => api.delete(`/invoices/${id}`),
  uploadOCR: (file) => {
    const formData = new FormData();
    formData.append('file', file);
    return api.post('/invoices/ocr', formData, {
      headers: { 'Content-Type': 'multipart/form-data' }
    });
  }
};

export const reportAPI = {
  generate606: (params) => api.get('/reports/606', { params }),
  preview606: (params) => api.get('/reports/606/preview', { params })
};

export const companyAPI = {
  getAll: () => api.get('/companies'),
  create: (data) => api.post('/companies', data),
  update: (id, data) => api.put(`/companies/${id}`, data),
  delete: (id) => api.delete(`/companies/${id}`)
};
```

### Componentes Principales

#### Layout Principal
```javascript
// components/Layout.jsx
import { useAuth } from '../hooks/useAuth';
import { Button } from './ui/button';

export const Layout = ({ children }) => {
  const { user, logout } = useAuth();

  const menuItems = user?.role === 'admin' ? [
    { name: 'Dashboard', path: '/', icon: Home },
    { name: 'Empresas', path: '/companies', icon: Building2 },
    { name: 'Reportes', path: '/reports', icon: FileText }
  ] : [
    { name: 'Dashboard', path: '/', icon: Home },
    { name: 'Facturas', path: '/invoices', icon: FileText },
    { name: 'Cargar Factura', path: '/upload', icon: Upload },
    { name: 'Reportes 606', path: '/reports', icon: BarChart3 },
    { name: 'Empleados', path: '/users', icon: Users }
  ];

  return (
    <div className="min-h-screen bg-gray-50 flex">
      <aside className="w-64 bg-white shadow-sm">
        <nav className="mt-8">
          {menuItems.map((item) => (
            <NavLink key={item.path} to={item.path} className="nav-item">
              <item.icon className="w-5 h-5" />
              {item.name}
            </NavLink>
          ))}
        </nav>
      </aside>
      
      <main className="flex-1">
        <header className="bg-white shadow-sm px-6 py-4">
          <div className="flex justify-between items-center">
            <h1 className="text-xl font-semibold">Sistema Facturas 606</h1>
            <Button onClick={logout} variant="outline">
              Cerrar Sesión
            </Button>
          </div>
        </header>
        
        <div className="p-6">
          {children}
        </div>
      </main>
    </div>
  );
};
```

#### Dashboard del Contador
```javascript
// components/Dashboard.jsx
import { useState, useEffect } from 'react';
import { Card, CardContent, CardHeader, CardTitle } from './ui/card';
import { invoiceAPI } from '../lib/api';

export const Dashboard = () => {
  const [stats, setStats] = useState({
    totalInvoices: 0,
    pendingInvoices: 0,
    totalAmount: 0,
    recentInvoices: []
  });

  useEffect(() => {
    fetchDashboardData();
  }, []);

  const fetchDashboardData = async () => {
    try {
      const response = await invoiceAPI.getAll({ limit: 5 });
      const invoices = response.data.invoices;
      
      setStats({
        totalInvoices: response.data.total,
        pendingInvoices: invoices.filter(inv => inv.status === 'pending').length,
        totalAmount: invoices.reduce((sum, inv) => sum + parseFloat(inv.amount), 0),
        recentInvoices: invoices
      });
    } catch (error) {
      console.error('Error fetching dashboard data:', error);
    }
  };

  return (
    <div className="space-y-6">
      <div className="grid grid-cols-1 md:grid-cols-3 gap-6">
        <Card>
          <CardHeader>
            <CardTitle>Total Facturas</CardTitle>
          </CardHeader>
          <CardContent>
            <div className="text-3xl font-bold">{stats.totalInvoices}</div>
          </CardContent>
        </Card>
        
        <Card>
          <CardHeader>
            <CardTitle>Pendientes</CardTitle>
          </CardHeader>
          <CardContent>
            <div className="text-3xl font-bold text-orange-600">
              {stats.pendingInvoices}
            </div>
          </CardContent>
        </Card>
        
        <Card>
          <CardHeader>
            <CardTitle>Monto Total</CardTitle>
          </CardHeader>
          <CardContent>
            <div className="text-3xl font-bold text-green-600">
              {formatCurrency(stats.totalAmount)}
            </div>
          </CardContent>
        </Card>
      </div>
      
      <Card>
        <CardHeader>
          <CardTitle>Facturas Recientes</CardTitle>
        </CardHeader>
        <CardContent>
          <div className="space-y-4">
            {stats.recentInvoices.map((invoice) => (
              <div key={invoice.id} className="flex justify-between items-center">
                <div>
                  <p className="font-medium">{invoice.supplier_name}</p>
                  <p className="text-sm text-gray-600">{invoice.ncf}</p>
                </div>
                <div className="text-right">
                  <p className="font-medium">{formatCurrency(invoice.amount)}</p>
                  <Badge variant={getStatusVariant(invoice.status)}>
                    {invoice.status}
                  </Badge>
                </div>
              </div>
            ))}
          </div>
        </CardContent>
      </Card>
    </div>
  );
};
```

### Componentes UI Base

El sistema utiliza shadcn/ui para componentes base consistentes:

```javascript
// components/ui/button.jsx
import { cn } from "../../lib/utils"

const Button = ({ className, variant = "default", size = "default", ...props }) => {
  return (
    <button
      className={cn(
        "inline-flex items-center justify-center rounded-md text-sm font-medium",
        "ring-offset-background transition-colors focus-visible:outline-none",
        "focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
        "disabled:pointer-events-none disabled:opacity-50",
        {
          "bg-primary text-primary-foreground hover:bg-primary/90": variant === "default",
          "bg-destructive text-destructive-foreground hover:bg-destructive/90": variant === "destructive",
          "border border-input bg-background hover:bg-accent": variant === "outline",
        },
        {
          "h-10 px-4 py-2": size === "default",
          "h-9 rounded-md px-3": size === "sm",
          "h-11 rounded-md px-8": size === "lg",
        },
        className
      )}
      {...props}
    />
  )
}
```

---

## Frontend Móvil - React

### Estructura del Proyecto

```
invoice-606-mobile/
├── src/
│   ├── App.jsx                 # Aplicación móvil principal
│   ├── App.css                 # Estilos móviles
│   └── components/ui/          # Componentes UI compartidos
├── index.html
├── package.json
└── vite.config.js
```

### Características Móviles

#### Captura de Cámara
```javascript
const CameraCapture = ({ onCapture, onCancel }) => {
  const videoRef = useRef(null);
  const canvasRef = useRef(null);
  const [stream, setStream] = useState(null);

  useEffect(() => {
    startCamera();
    return () => {
      if (stream) {
        stream.getTracks().forEach(track => track.stop());
      }
    };
  }, []);

  const startCamera = async () => {
    try {
      const mediaStream = await navigator.mediaDevices.getUserMedia({ 
        video: { facingMode: 'environment' } 
      });
      setStream(mediaStream);
      if (videoRef.current) {
        videoRef.current.srcObject = mediaStream;
      }
    } catch (err) {
      console.error('Error accessing camera:', err);
    }
  };

  const capturePhoto = () => {
    const video = videoRef.current;
    const canvas = canvasRef.current;
    const context = canvas.getContext('2d');
    
    canvas.width = video.videoWidth;
    canvas.height = video.videoHeight;
    context.drawImage(video, 0, 0);
    
    canvas.toBlob((blob) => {
      const file = new File([blob], 'invoice-photo.jpg', { type: 'image/jpeg' });
      onCapture(file);
    }, 'image/jpeg', 0.8);
  };

  return (
    <div className="fixed inset-0 bg-black">
      <video
        ref={videoRef}
        autoPlay
        playsInline
        className="w-full h-full object-cover"
      />
      <canvas ref={canvasRef} className="hidden" />
      
      <div className="absolute bottom-0 left-0 right-0 p-6">
        <div className="flex justify-center space-x-4">
          <Button onClick={onCancel} variant="outline">
            Cancelar
          </Button>
          <Button onClick={capturePhoto}>
            <Camera className="mr-2 h-5 w-5" />
            Capturar
          </Button>
        </div>
      </div>
    </div>
  );
};
```

#### Diseño Responsive
```css
/* App.css - Estilos móviles */
@media (max-width: 768px) {
  .container {
    padding: 1rem;
  }
  
  .grid {
    grid-template-columns: 1fr;
    gap: 1rem;
  }
  
  .button {
    width: 100%;
    padding: 0.75rem;
    font-size: 1rem;
  }
  
  .card {
    margin-bottom: 1rem;
    border-radius: 0.5rem;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  }
}

/* Touch-friendly interactions */
.touch-target {
  min-height: 44px;
  min-width: 44px;
}

.swipe-action {
  touch-action: pan-x;
}
```

---

## Base de Datos

### Esquema de Base de Datos

```sql
-- Tabla de empresas
CREATE TABLE company (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name VARCHAR(200) NOT NULL,
    rnc VARCHAR(20) UNIQUE NOT NULL,
    address TEXT,
    phone VARCHAR(20),
    email VARCHAR(100),
    is_active BOOLEAN DEFAULT 1,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);

-- Tabla de usuarios
CREATE TABLE user (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    first_name VARCHAR(50) NOT NULL,
    last_name VARCHAR(50) NOT NULL,
    username VARCHAR(50) UNIQUE NOT NULL,
    password_hash VARCHAR(255) NOT NULL,
    email VARCHAR(100),
    role VARCHAR(20) NOT NULL CHECK (role IN ('admin', 'accountant', 'employee')),
    company_id INTEGER,
    is_active BOOLEAN DEFAULT 1,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (company_id) REFERENCES company (id)
);

-- Tabla de tipos de gastos
CREATE TABLE expense_type (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name VARCHAR(100) NOT NULL,
    code VARCHAR(10),
    description TEXT,
    is_active BOOLEAN DEFAULT 1
);

-- Tabla de facturas
CREATE TABLE invoice (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    supplier_name VARCHAR(200) NOT NULL,
    rnc VARCHAR(20),
    ncf VARCHAR(20) NOT NULL,
    date DATE NOT NULL,
    amount DECIMAL(12, 2) NOT NULL,
    itbis DECIMAL(12, 2) DEFAULT 0,
    status VARCHAR(20) DEFAULT 'pending' CHECK (status IN ('pending', 'approved', 'rejected')),
    description TEXT,
    company_id INTEGER NOT NULL,
    user_id INTEGER NOT NULL,
    expense_type_id INTEGER NOT NULL,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (company_id) REFERENCES company (id),
    FOREIGN KEY (user_id) REFERENCES user (id),
    FOREIGN KEY (expense_type_id) REFERENCES expense_type (id)
);

-- Índices para optimización
CREATE INDEX idx_invoice_company_date ON invoice (company_id, date);
CREATE INDEX idx_invoice_ncf ON invoice (ncf);
CREATE INDEX idx_invoice_status ON invoice (status);
CREATE INDEX idx_user_company ON user (company_id);
```

### Datos de Prueba

```sql
-- Insertar tipos de gastos
INSERT INTO expense_type (name, code) VALUES
('Alquileres', '01'),
('Combustibles y Lubricantes', '02'),
('Suministros de Oficina', '03'),
('Servicios Técnicos', '04'),
('Publicidad y Promoción', '05'),
('Servicios Públicos', '06'),
('Mantenimiento y Reparaciones', '07'),
('Seguros', '08'),
('Servicios Profesionales', '09'),
('Materias Primas', '10'),
('Otros Gastos Operacionales', '11');

-- Insertar empresa de prueba
INSERT INTO company (name, rnc, address, phone, email) VALUES
('Empresa Demo S.A.', '123456789', 'Av. Principal #123, Santo Domingo', '809-555-0123', 'info@empresademo.com');

-- Insertar usuarios de prueba
INSERT INTO user (first_name, last_name, username, password_hash, email, role, company_id) VALUES
('Admin', 'Sistema', 'admin', '$2b$12$hash...', 'admin@sistema.com', 'admin', NULL),
('Juan', 'Pérez', 'contador1', '$2b$12$hash...', 'contador@empresademo.com', 'accountant', 1),
('María', 'García', 'empleado1', '$2b$12$hash...', 'empleado@empresademo.com', 'employee', 1);
```

### Migraciones

```python
# migrations/001_initial_schema.py
def upgrade():
    # Crear tablas
    op.create_table('company', ...)
    op.create_table('user', ...)
    op.create_table('expense_type', ...)
    op.create_table('invoice', ...)
    
    # Crear índices
    op.create_index('idx_invoice_company_date', 'invoice', ['company_id', 'date'])
    
def downgrade():
    # Eliminar en orden inverso
    op.drop_table('invoice')
    op.drop_table('expense_type')
    op.drop_table('user')
    op.drop_table('company')
```

---

## Integración OCR

### Configuración OpenAI

```python
# services/ocr_service.py
import openai
from PIL import Image
import base64
import io

class OCRService:
    def __init__(self):
        self.client = openai.OpenAI(
            api_key=os.getenv('OPENAI_API_KEY')
        )
    
    def extract_invoice_data(self, image_file):
        """Extrae datos de factura usando GPT-4 Vision"""
        
        # Procesar imagen
        image = Image.open(image_file)
        image = self.preprocess_image(image)
        
        # Convertir a base64
        buffered = io.BytesIO()
        image.save(buffered, format="JPEG")
        image_base64 = base64.b64encode(buffered.getvalue()).decode()
        
        # Prompt para extracción
        prompt = """
        Analiza esta imagen de factura dominicana y extrae los siguientes datos en formato JSON:
        {
          "supplier_name": "Nombre del proveedor",
          "rnc": "RNC del proveedor (9 dígitos)",
          "ncf": "NCF de la factura (formato B01XXXXXXXX)",
          "date": "Fecha en formato YYYY-MM-DD",
          "amount": "Monto total como número decimal",
          "itbis": "ITBIS como número decimal",
          "description": "Descripción del producto/servicio"
        }
        
        Si algún campo no es visible o legible, usa null.
        """
        
        try:
            response = self.client.chat.completions.create(
                model="gpt-4-vision-preview",
                messages=[
                    {
                        "role": "user",
                        "content": [
                            {"type": "text", "text": prompt},
                            {
                                "type": "image_url",
                                "image_url": {
                                    "url": f"data:image/jpeg;base64,{image_base64}"
                                }
                            }
                        ]
                    }
                ],
                max_tokens=500
            )
            
            # Parsear respuesta JSON
            content = response.choices[0].message.content
            return self.parse_json_response(content)
            
        except Exception as e:
            raise OCRException(f"Error en OCR: {str(e)}")
    
    def preprocess_image(self, image):
        """Preprocesa imagen para mejor OCR"""
        # Redimensionar si es muy grande
        max_size = (1024, 1024)
        image.thumbnail(max_size, Image.Resampling.LANCZOS)
        
        # Convertir a RGB si es necesario
        if image.mode != 'RGB':
            image = image.convert('RGB')
        
        return image
    
    def parse_json_response(self, content):
        """Parsea respuesta JSON del modelo"""
        try:
            # Extraer JSON del texto
            start = content.find('{')
            end = content.rfind('}') + 1
            json_str = content[start:end]
            
            data = json.loads(json_str)
            
            # Validar y limpiar datos
            return self.validate_extracted_data(data)
            
        except json.JSONDecodeError:
            raise OCRException("No se pudo parsear la respuesta JSON")
    
    def validate_extracted_data(self, data):
        """Valida y limpia datos extraídos"""
        validated = {}
        
        # Validar RNC (9 dígitos)
        rnc = data.get('rnc', '').replace('-', '').replace(' ', '')
        if rnc and rnc.isdigit() and len(rnc) == 9:
            validated['rnc'] = rnc
        
        # Validar NCF (formato B01XXXXXXXX)
        ncf = data.get('ncf', '').replace(' ', '').upper()
        if ncf and len(ncf) == 11 and ncf.startswith('B01'):
            validated['ncf'] = ncf
        
        # Validar fecha
        date_str = data.get('date')
        if date_str:
            try:
                validated['date'] = datetime.strptime(date_str, '%Y-%m-%d').date()
            except ValueError:
                pass
        
        # Validar montos
        for field in ['amount', 'itbis']:
            value = data.get(field)
            if value and isinstance(value, (int, float)):
                validated[field] = float(value)
        
        # Campos de texto
        for field in ['supplier_name', 'description']:
            value = data.get(field)
            if value and isinstance(value, str):
                validated[field] = value.strip()
        
        return validated
```

### Manejo de Errores OCR

```python
class OCRException(Exception):
    pass

def handle_ocr_error(error):
    """Maneja errores específicos de OCR"""
    error_messages = {
        'rate_limit': 'Límite de API alcanzado. Intente más tarde.',
        'invalid_image': 'Formato de imagen no válido.',
        'no_text_found': 'No se encontró texto legible en la imagen.',
        'api_error': 'Error en el servicio de OCR. Intente más tarde.'
    }
    
    return error_messages.get(error.type, 'Error desconocido en OCR')
```

---


## Seguridad

### Autenticación JWT

#### Configuración de Tokens
```python
JWT_CONFIG = {
    'SECRET_KEY': os.getenv('JWT_SECRET_KEY', 'default-secret'),
    'ALGORITHM': 'HS256',
    'ACCESS_TOKEN_EXPIRE_HOURS': 24,
    'REFRESH_TOKEN_EXPIRE_DAYS': 30
}

def create_access_token(user_data):
    payload = {
        'user_id': user_data['id'],
        'username': user_data['username'],
        'role': user_data['role'],
        'company_id': user_data['company_id'],
        'exp': datetime.utcnow() + timedelta(hours=JWT_CONFIG['ACCESS_TOKEN_EXPIRE_HOURS']),
        'iat': datetime.utcnow(),
        'type': 'access'
    }
    return jwt.encode(payload, JWT_CONFIG['SECRET_KEY'], algorithm=JWT_CONFIG['ALGORITHM'])
```

#### Middleware de Seguridad
```python
def security_headers(response):
    """Agrega headers de seguridad"""
    response.headers['X-Content-Type-Options'] = 'nosniff'
    response.headers['X-Frame-Options'] = 'DENY'
    response.headers['X-XSS-Protection'] = '1; mode=block'
    response.headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains'
    return response

def rate_limit_middleware():
    """Implementa rate limiting"""
    from flask_limiter import Limiter
    from flask_limiter.util import get_remote_address
    
    limiter = Limiter(
        app,
        key_func=get_remote_address,
        default_limits=["200 per day", "50 per hour"]
    )
    
    # Límites específicos para endpoints sensibles
    @limiter.limit("5 per minute")
    def login_rate_limit():
        pass
```

### Validación de Entrada

```python
from marshmallow import Schema, fields, validate, ValidationError

class InvoiceSchema(Schema):
    supplier_name = fields.Str(required=True, validate=validate.Length(min=1, max=200))
    rnc = fields.Str(validate=validate.Regexp(r'^\d{9}$'))
    ncf = fields.Str(required=True, validate=validate.Regexp(r'^B01\d{8}$'))
    date = fields.Date(required=True)
    amount = fields.Decimal(required=True, validate=validate.Range(min=0))
    itbis = fields.Decimal(validate=validate.Range(min=0))
    description = fields.Str(validate=validate.Length(max=500))
    expense_type_id = fields.Int(required=True)

def validate_invoice_data(data):
    schema = InvoiceSchema()
    try:
        return schema.load(data)
    except ValidationError as err:
        raise ValueError(f"Datos inválidos: {err.messages}")
```

### Encriptación de Contraseñas

```python
from werkzeug.security import generate_password_hash, check_password_hash
import bcrypt

class PasswordManager:
    @staticmethod
    def hash_password(password):
        """Genera hash seguro de contraseña"""
        return bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt()).decode('utf-8')
    
    @staticmethod
    def verify_password(password, hashed):
        """Verifica contraseña contra hash"""
        return bcrypt.checkpw(password.encode('utf-8'), hashed.encode('utf-8'))
    
    @staticmethod
    def validate_password_strength(password):
        """Valida fortaleza de contraseña"""
        if len(password) < 8:
            return False, "La contraseña debe tener al menos 8 caracteres"
        
        if not re.search(r'[A-Z]', password):
            return False, "La contraseña debe contener al menos una mayúscula"
        
        if not re.search(r'[a-z]', password):
            return False, "La contraseña debe contener al menos una minúscula"
        
        if not re.search(r'\d', password):
            return False, "La contraseña debe contener al menos un número"
        
        return True, "Contraseña válida"
```

### Autorización por Roles

```python
from functools import wraps

def require_role(*allowed_roles):
    def decorator(f):
        @wraps(f)
        def decorated_function(*args, **kwargs):
            if not hasattr(request, 'current_user'):
                return jsonify({'error': 'No autenticado'}), 401
            
            user_role = request.current_user.get('role')
            if user_role not in allowed_roles:
                return jsonify({'error': 'Acceso denegado'}), 403
            
            return f(*args, **kwargs)
        return decorated_function
    return decorator

# Uso en rutas
@app.route('/api/companies', methods=['POST'])
@require_auth
@require_role('admin')
def create_company():
    # Solo super admins pueden crear empresas
    pass

@app.route('/api/invoices', methods=['POST'])
@require_auth
@require_role('accountant', 'employee')
def create_invoice():
    # Contadores y empleados pueden crear facturas
    pass
```

---

## Despliegue

### Configuración de Producción

#### Variables de Entorno
```bash
# .env.production
FLASK_ENV=production
SECRET_KEY=your-super-secret-key-here
DATABASE_URL=postgresql://user:password@localhost/facturas606_prod
OPENAI_API_KEY=your-openai-api-key
CORS_ORIGINS=https://yourdomain.com,https://mobile.yourdomain.com
REDIS_URL=redis://localhost:6379/0
MAIL_SERVER=smtp.gmail.com
MAIL_PORT=587
MAIL_USERNAME=your-email@gmail.com
MAIL_PASSWORD=your-app-password
```

#### Configuración Flask para Producción
```python
# config.py
import os

class ProductionConfig:
    SECRET_KEY = os.environ.get('SECRET_KEY')
    SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL')
    SQLALCHEMY_TRACK_MODIFICATIONS = False
    
    # Configuración de seguridad
    SESSION_COOKIE_SECURE = True
    SESSION_COOKIE_HTTPONLY = True
    SESSION_COOKIE_SAMESITE = 'Lax'
    
    # Configuración de archivos
    MAX_CONTENT_LENGTH = 16 * 1024 * 1024  # 16MB max file size
    UPLOAD_FOLDER = '/var/uploads'
    
    # Configuración de logging
    LOG_LEVEL = 'INFO'
    LOG_FILE = '/var/log/facturas606/app.log'
    
    # Configuración de caché
    CACHE_TYPE = 'redis'
    CACHE_REDIS_URL = os.environ.get('REDIS_URL')
```

### Docker

#### Dockerfile para Backend
```dockerfile
# Dockerfile.backend
FROM python:3.11-slim

WORKDIR /app

# Instalar dependencias del sistema
RUN apt-get update && apt-get install -y \
    gcc \
    && rm -rf /var/lib/apt/lists/*

# Copiar requirements y instalar dependencias Python
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Copiar código de la aplicación
COPY src/ ./src/
COPY migrations/ ./migrations/

# Crear usuario no-root
RUN useradd --create-home --shell /bin/bash app
RUN chown -R app:app /app
USER app

# Exponer puerto
EXPOSE 5000

# Comando de inicio
CMD ["gunicorn", "--bind", "0.0.0.0:5000", "--workers", "4", "src.main:app"]
```

#### Dockerfile para Frontend
```dockerfile
# Dockerfile.frontend
FROM node:18-alpine as builder

WORKDIR /app

# Copiar package files
COPY package*.json ./
RUN npm ci --only=production

# Copiar código fuente
COPY . .

# Build de producción
RUN npm run build

# Imagen de producción con nginx
FROM nginx:alpine

# Copiar archivos build
COPY --from=builder /app/dist /usr/share/nginx/html

# Copiar configuración nginx
COPY nginx.conf /etc/nginx/nginx.conf

EXPOSE 80

CMD ["nginx", "-g", "daemon off;"]
```

#### Docker Compose
```yaml
# docker-compose.prod.yml
version: '3.8'

services:
  backend:
    build:
      context: ./invoice-606-backend
      dockerfile: Dockerfile
    environment:
      - FLASK_ENV=production
      - DATABASE_URL=postgresql://postgres:password@db:5432/facturas606
      - REDIS_URL=redis://redis:6379/0
    depends_on:
      - db
      - redis
    volumes:
      - uploads:/var/uploads
      - logs:/var/log/facturas606

  frontend-web:
    build:
      context: ./invoice-606-web
      dockerfile: Dockerfile
    ports:
      - "80:80"
    depends_on:
      - backend

  frontend-mobile:
    build:
      context: ./invoice-606-mobile
      dockerfile: Dockerfile
    ports:
      - "8080:80"
    depends_on:
      - backend

  db:
    image: postgres:15
    environment:
      - POSTGRES_DB=facturas606
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=password
    volumes:
      - postgres_data:/var/lib/postgresql/data

  redis:
    image: redis:7-alpine
    volumes:
      - redis_data:/data

  nginx:
    image: nginx:alpine
    ports:
      - "443:443"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf
      - ./ssl:/etc/nginx/ssl
    depends_on:
      - frontend-web
      - frontend-mobile
      - backend

volumes:
  postgres_data:
  redis_data:
  uploads:
  logs:
```

### Configuración Nginx

```nginx
# nginx.conf
upstream backend {
    server backend:5000;
}

upstream frontend_web {
    server frontend-web:80;
}

upstream frontend_mobile {
    server frontend-mobile:80;
}

server {
    listen 443 ssl http2;
    server_name yourdomain.com;

    ssl_certificate /etc/nginx/ssl/cert.pem;
    ssl_certificate_key /etc/nginx/ssl/key.pem;

    # Configuración SSL
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512;
    ssl_prefer_server_ciphers off;

    # Headers de seguridad
    add_header Strict-Transport-Security "max-age=63072000" always;
    add_header X-Frame-Options DENY;
    add_header X-Content-Type-Options nosniff;

    # Aplicación web principal
    location / {
        proxy_pass http://frontend_web;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    # API Backend
    location /api/ {
        proxy_pass http://backend;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        
        # Configuración para uploads
        client_max_body_size 16M;
        proxy_read_timeout 300s;
        proxy_connect_timeout 75s;
    }
}

server {
    listen 443 ssl http2;
    server_name mobile.yourdomain.com;

    ssl_certificate /etc/nginx/ssl/cert.pem;
    ssl_certificate_key /etc/nginx/ssl/key.pem;

    # Aplicación móvil
    location / {
        proxy_pass http://frontend_mobile;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

# Redirección HTTP a HTTPS
server {
    listen 80;
    server_name yourdomain.com mobile.yourdomain.com;
    return 301 https://$server_name$request_uri;
}
```

---

## Monitoreo y Logs

### Configuración de Logging

```python
# logging_config.py
import logging
import logging.handlers
import os

def setup_logging(app):
    """Configura logging para la aplicación"""
    
    # Crear directorio de logs si no existe
    log_dir = '/var/log/facturas606'
    os.makedirs(log_dir, exist_ok=True)
    
    # Configurar formato
    formatter = logging.Formatter(
        '%(asctime)s %(levelname)s %(name)s %(funcName)s():%(lineno)d %(message)s'
    )
    
    # Handler para archivo
    file_handler = logging.handlers.RotatingFileHandler(
        f'{log_dir}/app.log',
        maxBytes=10485760,  # 10MB
        backupCount=5
    )
    file_handler.setFormatter(formatter)
    file_handler.setLevel(logging.INFO)
    
    # Handler para errores
    error_handler = logging.handlers.RotatingFileHandler(
        f'{log_dir}/error.log',
        maxBytes=10485760,
        backupCount=5
    )
    error_handler.setFormatter(formatter)
    error_handler.setLevel(logging.ERROR)
    
    # Configurar logger de la aplicación
    app.logger.addHandler(file_handler)
    app.logger.addHandler(error_handler)
    app.logger.setLevel(logging.INFO)
    
    # Logger para auditoría
    audit_logger = logging.getLogger('audit')
    audit_handler = logging.handlers.RotatingFileHandler(
        f'{log_dir}/audit.log',
        maxBytes=10485760,
        backupCount=10
    )
    audit_handler.setFormatter(formatter)
    audit_logger.addHandler(audit_handler)
    audit_logger.setLevel(logging.INFO)
    
    return audit_logger

# Middleware de auditoría
def log_request(request, response, user_id=None):
    """Registra requests para auditoría"""
    audit_logger = logging.getLogger('audit')
    
    audit_data = {
        'user_id': user_id,
        'method': request.method,
        'url': request.url,
        'ip': request.remote_addr,
        'user_agent': request.headers.get('User-Agent'),
        'status_code': response.status_code,
        'timestamp': datetime.utcnow().isoformat()
    }
    
    audit_logger.info(f"REQUEST: {audit_data}")
```

### Métricas de Aplicación

```python
# metrics.py
from prometheus_client import Counter, Histogram, Gauge, generate_latest
import time

# Contadores
REQUEST_COUNT = Counter('http_requests_total', 'Total HTTP requests', ['method', 'endpoint', 'status'])
REQUEST_DURATION = Histogram('http_request_duration_seconds', 'HTTP request duration')
ACTIVE_USERS = Gauge('active_users_total', 'Number of active users')
INVOICE_COUNT = Counter('invoices_created_total', 'Total invoices created')
OCR_REQUESTS = Counter('ocr_requests_total', 'Total OCR requests', ['status'])

def track_request(f):
    """Decorator para trackear métricas de requests"""
    @wraps(f)
    def decorated_function(*args, **kwargs):
        start_time = time.time()
        
        try:
            response = f(*args, **kwargs)
            status = response.status_code if hasattr(response, 'status_code') else 200
            REQUEST_COUNT.labels(
                method=request.method,
                endpoint=request.endpoint,
                status=status
            ).inc()
            
            return response
            
        except Exception as e:
            REQUEST_COUNT.labels(
                method=request.method,
                endpoint=request.endpoint,
                status=500
            ).inc()
            raise
            
        finally:
            REQUEST_DURATION.observe(time.time() - start_time)
    
    return decorated_function

@app.route('/metrics')
def metrics():
    """Endpoint para métricas de Prometheus"""
    return generate_latest()
```

### Monitoreo de Salud

```python
# health_check.py
from flask import jsonify
import psutil
import time

@app.route('/health')
def health_check():
    """Endpoint de health check"""
    
    health_status = {
        'status': 'healthy',
        'timestamp': datetime.utcnow().isoformat(),
        'version': '1.0.0',
        'checks': {}
    }
    
    # Check de base de datos
    try:
        db.session.execute('SELECT 1')
        health_status['checks']['database'] = 'healthy'
    except Exception as e:
        health_status['checks']['database'] = f'unhealthy: {str(e)}'
        health_status['status'] = 'unhealthy'
    
    # Check de Redis (si se usa)
    try:
        # redis_client.ping()
        health_status['checks']['redis'] = 'healthy'
    except Exception as e:
        health_status['checks']['redis'] = f'unhealthy: {str(e)}'
    
    # Check de OpenAI API
    try:
        # Verificar conectividad con OpenAI
        health_status['checks']['openai'] = 'healthy'
    except Exception as e:
        health_status['checks']['openai'] = f'unhealthy: {str(e)}'
    
    # Métricas del sistema
    health_status['system'] = {
        'cpu_percent': psutil.cpu_percent(),
        'memory_percent': psutil.virtual_memory().percent,
        'disk_percent': psutil.disk_usage('/').percent
    }
    
    status_code = 200 if health_status['status'] == 'healthy' else 503
    return jsonify(health_status), status_code

@app.route('/ready')
def readiness_check():
    """Endpoint de readiness para Kubernetes"""
    # Verificar que la aplicación esté lista para recibir tráfico
    return jsonify({'status': 'ready'}), 200

@app.route('/live')
def liveness_check():
    """Endpoint de liveness para Kubernetes"""
    # Verificar que la aplicación esté viva
    return jsonify({'status': 'alive'}), 200
```

---

## APIs Externas

### Integración OpenAI

```python
# openai_client.py
import openai
from tenacity import retry, stop_after_attempt, wait_exponential

class OpenAIClient:
    def __init__(self, api_key):
        self.client = openai.OpenAI(api_key=api_key)
    
    @retry(
        stop=stop_after_attempt(3),
        wait=wait_exponential(multiplier=1, min=4, max=10)
    )
    def extract_invoice_data(self, image_base64):
        """Extrae datos de factura con retry automático"""
        
        response = self.client.chat.completions.create(
            model="gpt-4-vision-preview",
            messages=[{
                "role": "user",
                "content": [
                    {"type": "text", "text": self.get_extraction_prompt()},
                    {
                        "type": "image_url",
                        "image_url": {"url": f"data:image/jpeg;base64,{image_base64}"}
                    }
                ]
            }],
            max_tokens=500,
            temperature=0.1
        )
        
        return response.choices[0].message.content
    
    def get_extraction_prompt(self):
        return """
        Analiza esta factura dominicana y extrae los siguientes datos en formato JSON válido:
        {
          "supplier_name": "Nombre completo del proveedor",
          "rnc": "RNC del proveedor (solo números, 9 dígitos)",
          "ncf": "NCF de la factura (formato B01XXXXXXXX)",
          "date": "Fecha en formato YYYY-MM-DD",
          "amount": "Monto total sin símbolos, solo número decimal",
          "itbis": "ITBIS/impuesto como número decimal",
          "description": "Descripción del producto o servicio"
        }
        
        Reglas importantes:
        - Si un campo no es visible o legible, usa null
        - Para montos, usa solo números y punto decimal
        - Para RNC, solo los 9 dígitos sin guiones
        - Para NCF, debe seguir el formato exacto B01XXXXXXXX
        - Para fecha, usa formato ISO (YYYY-MM-DD)
        """
```

### Configuración de Rate Limiting

```python
# rate_limiting.py
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
import redis

# Configurar Redis para rate limiting
redis_client = redis.Redis(host='localhost', port=6379, db=1)

limiter = Limiter(
    app,
    key_func=get_remote_address,
    storage_uri="redis://localhost:6379/1",
    default_limits=["1000 per day", "100 per hour"]
)

# Límites específicos por endpoint
@app.route('/api/auth/login', methods=['POST'])
@limiter.limit("5 per minute")
def login():
    pass

@app.route('/api/invoices/ocr', methods=['POST'])
@limiter.limit("10 per minute")
def ocr_upload():
    pass

@app.route('/api/reports/606', methods=['GET'])
@limiter.limit("20 per hour")
def generate_report():
    pass
```

---

## Conclusión

Esta documentación técnica proporciona una visión completa del sistema de gestión de facturas 606, incluyendo:

- **Arquitectura modular** con separación clara de responsabilidades
- **APIs RESTful** bien documentadas y seguras
- **Integración OCR** robusta con manejo de errores
- **Seguridad multicapa** con autenticación JWT y validación
- **Configuración de producción** lista para despliegue
- **Monitoreo y logging** comprehensivo
- **Escalabilidad** preparada para crecimiento

El sistema está diseñado siguiendo las mejores prácticas de desarrollo y está listo para su implementación en producción.

---

**Versión**: 1.0  
**Fecha**: 27 de Agosto, 2024  
**Autor**: Sistema de Gestión de Facturas 606

