CRUD contable con Python, MySQL y PHP
Vas a construir ContaPyme, un sistema administrativo para registrar terceros (clientes y proveedores) y sus movimientos de ingreso y egreso. Primero lo usas y ves qué ocurre en cada capa; después escribes cada pieza: base de datos, clases, repositorio, reglas de negocio, reportes y la conexión con una página web en PHP.
- Arquitectura en capas
- Modelo relacional en MySQL
- POO: herencia y polimorfismo
- Patrón repositorio
- Consultas parametrizadas
- Reglas de negocio
- Reportes con SQL
- Integración Python y PHP
Si quieres construir el CRUD como aplicación web en tu computador, con WampServer, phpMyAdmin y VS Code, sigue el taller guiado paso a paso. Trae pantallazos de cada paso y el proyecto terminado para comparar.
recursos/ contiene el proyecto completo en archivos reales para abrir en VS Code y MySQL Workbench.El caso: ContaPyme
Una pequeña empresa de Cali lleva sus cuentas en una hoja de cálculo que ya no da abasto. Necesita un sistema que cumpla estos requerimientos:
| Código | Como usuario quiero… | Operación |
|---|---|---|
| RF-01 | registrar clientes y proveedores con NIT, razón social y correo | Create |
| RF-02 | consultar y buscar terceros por nombre o NIT | Read |
| RF-03 | corregir los datos de un tercero | Update |
| RF-04 | eliminar un tercero, siempre que no tenga movimientos | Delete |
| RF-05 | registrar ingresos y egresos asociados a un tercero | Create |
| RF-06 | ver el balance: total de ingresos, egresos y saldo por tercero | Read (reporte) |
El sistema se organiza en capas. Cada una tiene una sola responsabilidad y solo conversa con la capa vecina:
Es el mismo patrón que usaste con DAO o repositorios en Java. Cambia la sintaxis, no la arquitectura.
Consola CRUD en vivo
Usa ContaPyme como lo haría un usuario. Cada vez que hagas algo, el panel de traza (a la derecha, o debajo en pantallas pequeñas) muestra lo que ocurre por dentro: la petición HTTP que envía el navegador, el código del backend en Python y en PHP, la sentencia SQL y la respuesta. Provoca errores a propósito: repite un NIT, deja campos vacíos o intenta eliminar un tercero que tiene movimientos.
| NIT | Razón social | Tipo |
|---|
| Fecha | Tercero | Concepto | Valor |
|---|
| Tercero | Ingresos | Egresos | Saldo |
|---|
Traza de la operación
Modelo de datos en MySQL
La base de datos es la última línea de defensa. Aunque el código tenga errores, las restricciones impiden datos inválidos: UNIQUE evita NIT repetidos, FOREIGN KEY impide movimientos sin tercero, CHECK rechaza valores negativos. Ejecuta este script en MySQL Workbench.
| Decisión de diseño | Por qué |
|---|---|
DECIMAL(14,2) para dinero | FLOAT guarda aproximaciones; en contabilidad un peso de diferencia es un descuadre. |
ON DELETE RESTRICT | Borrar un tercero con historial contable destruiría la trazabilidad. La base lo impide (RF-04). |
ENUM para tipo | Solo acepta los valores del negocio; evita "cliente", "Cliente" y "clientes" como si fueran distintos. |
Índice en fecha | Los reportes filtran por periodo; el índice (un árbol B+, visto en el laboratorio 2) acelera la búsqueda. |
utf8mb4 | Guarda tildes, eñes y emojis sin corromperlos. |
Clases del dominio: herencia y polimorfismo
Cada tabla tiene su clase. @dataclass genera automáticamente el constructor, __repr__ y la comparación, lo que en Java escribías a mano. La validación va en __post_init__: un objeto inválido nunca llega a existir.
Movimiento es una clase abstracta; Ingreso y Egreso la heredan y cada una implementa signo() a su manera. Eso es polimorfismo: el balance llama al mismo método sin preguntar de qué tipo es cada movimiento.
Un tercer tipo de movimiento
Agrega la clase Ajuste, que hereda de Movimiento, tiene tipo = "AJUSTE" y cuyo signo es 1 si el concepto empieza con "+" y -1 en caso contrario. Imprime el efecto en caja de Ajuste(1, "-Faltante de caja", 20000, date(2026, 9, 30)): -20000
Para la sustentación: pide que expliquen por qué agregar Ajuste no obligó a modificar el cálculo del saldo. Es el principio abierto/cerrado.
La conexión a la base de datos
Python habla con MySQL mediante un conector. En clase usarás mysql-connector-python; en este laboratorio los ejemplos ejecutables usan sqlite3, una base de datos que viene incluida en Python y funciona en el navegador. El código del repositorio es el mismo; solo cambian la conexión y el marcador de parámetros.
| Concepto | mysql-connector | sqlite3 | PDO |
|---|---|---|---|
| Marcador de parámetro | %s | ? | ? o :nombre |
| Ejecutar | cur.execute(sql, datos) | cur.execute(sql, datos) | $stmt->execute($datos) |
| Traer filas | fetchall() | fetchall() | fetchAll() |
| Id generado | cur.lastrowid | cur.lastrowid | $pdo->lastInsertId() |
| Confirmar cambios | con.commit() | con.commit() | automático, o commit() en transacción |
Olvidar con.commit(). El INSERT parece funcionar, pero al cerrar el programa los datos desaparecen. El gestor de contexto de arriba lo hace por ti.
El repositorio: las cuatro operaciones
El repositorio es la única clase que escribe SQL. El resto del programa le pide cosas con métodos claros (crear, listar, actualizar, eliminar) y no sabe si detrás hay MySQL, SQLite o un archivo. Compara cada operación en los tres lenguajes.
Create: crear
Read: consultar
Update: actualizar
Delete: eliminar
Taller: CRUD completo ejecutable
Este es el sistema completo en un solo archivo, funcionando con Python real en tu navegador. Modifícalo, rómpelo y vuelve a ejecutarlo. Cuando lo pases a tu proyecto, separa cada bloque en su archivo (modelos.py, conexion.py, repositorios.py, main.py) y cambia ? por %s.
Contar por tipo
Agrega al repositorio el método contar_por_tipo() que use SELECT tipo, COUNT(*) … GROUP BY tipo ORDER BY tipo y devuelva un diccionario. El código de prueba ya está escrito; la salida esperada es {'CLIENTE': 2, 'PROVEEDOR': 1}
Nota que dict(filas) funciona porque cada fila es una tupla de dos elementos: es la conversión lista de pares a diccionario del laboratorio 2.
Reglas de negocio y seguridad
Un dato se valida en tres lugares, y cada uno protege algo distinto:
| Capa | Qué valida | Ejemplo en ContaPyme | Si falta |
|---|---|---|---|
| Vista (HTML, JS) | Formato, para ayudar al usuario | required, type="email" | Mala experiencia, pero se puede saltar |
| Modelo (Python o PHP) | Reglas del negocio | NIT de 6 a 10 dígitos, valor mayor que cero | Entran datos absurdos por la API |
| Base de datos | Integridad | UNIQUE, FOREIGN KEY, CHECK | Duplicados y registros huérfanos |
La regla más importante de todo el laboratorio: nunca construyas SQL pegando texto del usuario. Ejecuta este ejemplo y mira cómo una búsqueda "inocente" devuelve todos los registros y otra borra la tabla.
Las contraseñas nunca se guardan en texto plano: en Python se usa hashlib o bcrypt, en PHP password_hash() y password_verify(). Y todo lo que se imprime en HTML pasa por htmlspecialchars() para evitar que un nombre como <script> se ejecute en el navegador de otro usuario.
clave por un hash con hashlib.sha256 y explicar por qué aun así no es suficiente sin sal (por eso se prefiere bcrypt).Reportes: el balance
Un reporte se puede calcular en SQL o en Python. Regla práctica: filtra y agrupa en SQL (la base de datos lo hace más rápido y envía menos datos) y deja a Python el formato y los cálculos que SQL no expresa bien.
Mayor cliente
Escribe una consulta que devuelva el nombre del tercero con más ingresos y su total. Salida esperada: Ferretería Central 1420000.0
Integración con la web: Python, PHP y MySQL
Python y PHP no se mezclan en el mismo archivo. Se integran de tres maneras, y elegir la correcta depende de dónde se va a ejecutar cada parte:
| Estrategia | Cómo funciona | Dónde corre | Cuándo usarla |
|---|---|---|---|
| A. Base de datos compartida | Python (consola o escritorio) y PHP (web) leen y escriben la misma base MySQL. | PHP y MySQL en Hostinger; Python en el equipo, conectado por MySQL remoto. | La más simple. Recomendada para el proyecto del curso. |
| B. API en PHP, cliente en Python | PHP expone api.php que responde JSON; Python la consume con requests. | API en Hostinger; Python en cualquier equipo con internet. | Cuando Python no debe tener la contraseña de la base de datos. |
| C. API en Python (Flask) | Python expone las rutas; la página web (HTML, JS o PHP) las consume. | Requiere un servidor que ejecute Python de forma permanente, por ejemplo un VPS. | Cuando el backend completo debe ser Python. Practícala en tu equipo con el taller guiado de Flask. |
Los planes de hosting compartido suelen ejecutar PHP y MySQL, pero no permiten dejar una aplicación Python escuchando peticiones. Por eso, si la página debe quedar publicada en un hosting compartido, las estrategias A y B son las viables. Verifica las condiciones de tu plan antes de decidir.
La API en PHP (estrategia B)
Un solo archivo atiende las cuatro operaciones según el método HTTP: GET consulta, POST crea, PUT actualiza, DELETE elimina. Es el mismo contrato que viste en la consola CRUD del módulo 2.
El cliente en Python
La página web que consume la API
Los archivos completos (script SQL, proyecto Python por capas con menú, API en PHP y panel web) se descargan en contapyme-kit.zip, que incluye un archivo LEEME.md que explica cómo instalarlos en el equipo y en el hosting.
Proyecto integrador y evaluación
Construye tu propio sistema administrativo con la misma arquitectura. Elige un dominio distinto a ContaPyme: inventario de una tienda, control de cartera, nómina simplificada, facturación de un taller o reservas de un consultorio.
| Fase | Entregable | Laboratorio de apoyo |
|---|---|---|
| 1. Migración Java a Python | Dos clases de un proyecto Java anterior reescritas en Python con @dataclass y @property | Lab 1, módulo 12 |
| 2. Herencia y polimorfismo | Jerarquía con clase abstracta y al menos dos subclases con comportamiento propio | Lab 3, módulo 4 |
| 3. Modelo de datos | Script SQL con mínimo dos tablas relacionadas, UNIQUE, FOREIGN KEY y CHECK; diagrama en Workbench | Lab 3, módulo 3 |
| 4. CRUD | Repositorio con las cuatro operaciones, consultas parametrizadas y manejo de errores de integridad | Lab 3, módulos 6 y 7 |
| 5. Integración | Menú de consola en Python y una vista web en PHP sobre la misma base de datos (estrategia A o B) | Lab 3, módulos 10 y 11 |
| 6. Sustentación | Demostración en vivo, explicación de una decisión de diseño y modificación del código en el momento | Todo el curso |
Rúbrica
| Criterio | Superior (4.6 a 5.0) | Alto (4.0 a 4.5) | Básico (3.0 a 3.9) | Bajo (1.0 a 2.9) |
|---|---|---|---|---|
| POO | Herencia, clase abstracta y polimorfismo usados donde aportan; validación en el modelo | Herencia correcta, validación parcial | Clases sin herencia real | Código sin clases o que no ejecuta |
| Base de datos | Restricciones completas; el sistema maneja cada error de integridad con un mensaje claro | Relaciones correctas, manejo parcial de errores | Tablas sin claves foráneas | Sin base de datos funcional |
| CRUD y seguridad | Cuatro operaciones parametrizadas, búsqueda y reporte con GROUP BY | Cuatro operaciones parametrizadas | CRUD incompleto o con SQL concatenado | Menos de dos operaciones |
| Integración | Python y PHP operan sobre los mismos datos y se demuestra en vivo | Integración funcional con fallas menores | Solo una de las dos partes funciona | Sin integración |
| Sustentación | Explica decisiones y modifica el código en vivo sin ayuda | Explica con claridad, modifica con apoyo | Describe el código sin justificarlo | No puede explicar su código |